• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / blurts-server / #11967

pending completion
#11967

push

circleci

web-flow
MNTOR-1038 refactor email utility for V2 (#2772)

* refactor email utility MNTOR-1038
* add testdouble mocking library per ADR 0000

282 of 1202 branches covered (23.46%)

Branch coverage included in aggregate %.

58 of 58 new or added lines in 3 files covered. (100.0%)

959 of 3205 relevant lines covered (29.92%)

2.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/utils/email.js
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4

5
import { URL } from 'url'
6

7
import AppConstants from '../app-constants.js'
8

9
import { createTransport } from 'nodemailer'
10
import mozlog from './log.js'
11

12
// TODO
13
// import { getTemplate, verifyPartial } from '../views/email-2022.js'
14

15
const log = mozlog('email-utils')
×
16

17
// The SMTP transport object. This is initialized to a nodemailer transport
18
// object while reading SMTP credentials, or to a dummy function in debug mode.
19
let gTransporter
20

21
async function initEmail (smtpUrl = AppConstants.SMTP_URL) {
×
22
  // Allow a debug mode that will log JSON instead of sending emails.
23
  if (!smtpUrl) {
×
24
    log.info('smtpUrl-empty', { message: 'EmailUtils will log a JSON response instead of sending emails.' })
×
25
    gTransporter = createTransport({ jsonTransport: true })
×
26
    return true
×
27
  }
28

29
  gTransporter = createTransport(smtpUrl)
×
30
  const gTransporterVerification = await gTransporter.verify()
×
31
  return gTransporterVerification
×
32
}
33

34
/**
35
 * Send Email
36
 * @param {string} recipient
37
 * @param {string} subject
38
 * @param {function} template
39
 * @param {object} data
40
 * @returns <Promise>
41
 */
42
function sendEmail (recipient, subject, template, data) {
43
  if (!gTransporter) {
×
44
    return Promise.reject(new Error('SMTP transport not initialized'))
×
45
  }
46

47
  // const html = getTemplate(verifyPartial, data)
48
  // TODO implement when email template is ready
49
  const html = '<html>placeholder</html>'
×
50
  return new Promise((resolve, reject) => {
×
51
    const emailFrom = AppConstants.EMAIL_FROM
×
52
    const mailOptions = {
×
53
      from: emailFrom,
54
      to: recipient,
55
      subject,
56
      html,
57
      headers: {
58
        'x-ses-configuration-set': AppConstants.SES_CONFIG_SET
59
      }
60
    }
61

62
    gTransporter.sendMail(mailOptions, (error, info) => {
×
63
      if (error) {
×
64
        reject(new Error(error))
×
65
        return
×
66
      }
67
      if (gTransporter.transporter.name === 'JSONTransport') {
×
68
        log.info('JSONTransport', { message: info.message.toString() })
×
69
      }
70
      resolve(info)
×
71
    })
72
  })
73
}
74

75
function appendUtmParams (url, campaign, content) {
76
  const utmParameters = {
×
77
    utm_source: 'fx-monitor',
78
    utm_medium: 'email',
79
    utm_campaign: campaign,
80
    utm_content: content
81
  }
82
  for (const param in utmParameters) {
×
83
    url.searchParams.append(param, utmParameters[param])
×
84
  }
85
  return url
×
86
}
87

88
function getEmailCtaHref (emailType, content, subscriberId = null) {
×
89
  const subscriberParamPath = (subscriberId) ? `/?subscriber_id=${subscriberId}` : '/'
×
90
  const url = new URL(subscriberParamPath, AppConstants.SERVER_URL)
×
91
  return appendUtmParams(url, emailType, content)
×
92
}
93

94
function getVerificationUrl (subscriber) {
95
  if (!subscriber.verification_token) throw new Error('subscriber has no verification_token')
×
96
  let url = new URL(`${AppConstants.SERVER_URL}/api/v1/user/verify-email`)
×
97
  url = appendUtmParams(url, 'verified-subscribers', 'account-verification-email')
×
98
  url.searchParams.append('token', encodeURIComponent(subscriber.verification_token))
×
99
  return url
×
100
}
101

102
function getUnsubscribeUrl (subscriber, emailType) {
103
  // TODO: email unsubscribe is broken for most emails
104
  let url = new URL(`${AppConstants.SERVER_URL}/user/unsubscribe`)
×
105
  const token = (Object.prototype.hasOwnProperty.call(subscriber, 'verification_token')) ? subscriber.verification_token : subscriber.primary_verification_token
×
106
  const hash = (Object.prototype.hasOwnProperty.call(subscriber, 'sha1')) ? subscriber.sha1 : subscriber.primary_sha1
×
107
  url.searchParams.append('token', encodeURIComponent(token))
×
108
  url.searchParams.append('hash', encodeURIComponent(hash))
×
109
  url = appendUtmParams(url, 'unsubscribe', emailType)
×
110
  return url
×
111
}
112

113
function getMonthlyUnsubscribeUrl (subscriber, campaign, content) {
114
  // TODO: create new subscriptions section in settings to manage all emails and avoid one-off routes like this
115
  if (!subscriber.primary_verification_token) throw new Error('subscriber has no primary verification_token')
×
116
  let url = new URL('user/unsubscribe-monthly/', AppConstants.SERVER_URL)
×
117

118
  url = appendUtmParams(url, campaign, content)
×
119
  url.searchParams.append('token', encodeURIComponent(subscriber.primary_verification_token))
×
120

121
  return url
×
122
}
123

124
export {
125
  initEmail,
126
  sendEmail,
127
  getEmailCtaHref,
128
  getVerificationUrl,
129
  getUnsubscribeUrl,
130
  getMonthlyUnsubscribeUrl
131
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc