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

mozilla / blurts-server / #13087

pending completion
#13087

push

circleci

web-flow
Merge pull request #2960 from mozilla/MNTOR-1496-Fix-report-email-breach-icons

Use cached logos and fallback icons for breach email

282 of 1603 branches covered (17.59%)

Branch coverage included in aggregate %.

12 of 12 new or added lines in 5 files covered. (100.0%)

959 of 4331 relevant lines covered (22.14%)

1.85 hits per line

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

0.0
/src/views/emails/email-2022.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 AppConstants from '../../app-constants.js'
6
import { getMessage } from '../../utils/fluent.js'
7

8
const companyAddress = '2 Harrison St. #175, San Francisco, California 94105 USA'
×
9
const links = (data) => ({
×
10
  faq: 'https://support.mozilla.org/kb/firefox-monitor-faq',
11
  hibp: 'https://haveibeenpwned.com/',
12
  legal: `https://www.mozilla.org/about/legal?utm_source=fx-monitor&utm_medium=email&utm_campaign=${data.utmCampaign}&utm_content=email-footer-link`,
13
  termsAndPrivacy: `https://www.mozilla.org/privacy/firefox-monitor?utm_source=fx-monitor&utm_medium=email&utm_campaign=${data.utmCampaign}&utm_content=email-footer-link`
14
})
15

16
const images = {
×
17
  header: `${AppConstants.SERVER_URL}/images/email/person-at-desk.png`,
18
  footer: `${AppConstants.SERVER_URL}/images/email/mozilla-logo-bw.png`,
19
  logoDark: `${AppConstants.SERVER_URL}/images/email/monitor-logo-transparent-dark-mode.png`,
20
  logoLight: `${AppConstants.SERVER_URL}/images/email/monitor-logo-bg-light.png`
21
}
22

23
const bodyStyle = `
×
24
  color: black;
25
  font: normal 16px/1.2 sans-serif;
26
`
27

28
const tableStyle = `
×
29
  margin: auto;
30
  max-width: 1080px;
31
  text-align: center;
32
  width: 100%;
33
`
34

35
const headerTableStyle = `
×
36
  background-color: #321c64;
37
  color: white;
38
  height: 331px;
39
  text-align: left;
40
  width: 100%;
41
`
42

43
const headerImageContainerStyle = `
×
44
  background-color: #321c64;
45
  vertical-align: bottom;
46
  width: 50%;
47
`
48

49
const headerImageStyle = `
×
50
  display: block;
51
  margin-left: auto;
52
  max-width: 100%;
53
  object-fit: cover;
54
  object-position: left;
55
`
56

57
const footerContainerStyle = `
×
58
  background: #f9f9fa;
59
  border-top: 1px solid #dddddd;
60
  padding: 24px 0;
61
`
62

63
const footerImageStyle = `
×
64
  display: block;
65
  margin: 24px auto 0;
66
`
67

68
const emailHeader = (data) => `
×
69
  <tr class='logo'>
70
    <td height='100'></td>
71
  </tr>
72
  <tr class='header'>
73
    <td>
74
      <table
75
        border='0'
76
        cellpadding='0'
77
        cellspacing='0'
78
        role='presentation'
79
        style='${headerTableStyle}'
80
      >
81
        <tr>
82
          <td>
83
            <h1>
84
              ${getMessage(data.heading)}
85
            </h1>
86
            ${data.subhead !== ''
×
87
              ? `<p>${getMessage(data.subhead)}</p>`
88
              : ''
89
            }
90
          </td>
91
          <td
92
            class='header-image'
93
            style='${headerImageContainerStyle}'
94
          >
95
            <img
96
              alt=''
97
              height='331'
98
              src='${images.header}'
99
              style='${headerImageStyle}'
100
              width='476'
101
            >
102
          </td>
103
        </tr>
104
      </table>
105
    </td>
106
  </tr>
107
`
108

109
function getUnsubscribeCopy (data) {
110
  const unsubLink = `<a href='${data.unsubscribeUrl}'>${getMessage('email-unsub-link')}</a>`
×
111
  const faqLink = `<a href='${links(data).faq}'>${getMessage('frequently-asked-questions')}</a>`
×
112

113
  return getMessage('email-footer-blurb', {
×
114
    unsubLink,
115
    faqLink
116
  })
117
}
118

119
const emailFooter = (data) => `
×
120
  <tr class='footer'>
121
    <td style='${footerContainerStyle}'>
122
      ${
123
        data.unsubscribeUrl
×
124
          ? `<p>${getUnsubscribeCopy(data)}</p>`
125
          : ''
126
      }
127
      <p>
128
        ${getMessage('email-2022-hibp-attribution', {
129
          'hibp-link-attr': `href='${links(data).hibp}' rel='noopener'`
130
        })}
131
      </p>
132
      <img
133
        alt='${getMessage('mozilla')}'
134
        src='${images.footer}'
135
        style='${footerImageStyle}'
136
        width='130px'
137
      >
138
      <p>
139
        ${companyAddress}
140
      </p>
141
      <p>
142
        <a href='${links(data).legal}'>
143
          ${getMessage('legal')}
144
        </a>
145
        ${' • '}
146
        <a href='${links(data).termsAndPrivacy}'>
147
          ${getMessage('terms-and-privacy')}
148
        </a>
149
      </p>
150
    </td>
151
  </tr>
152
`
153

154
const getStyles = () => `
×
155
  <style>
156
    .email-body,
157
    .email-body * {
158
      margin: 0;
159
      padding: 0;
160
    }
161

162
    body.email-body,
163
    .email-container {
164
      color: black;
165
      font: normal 16px/1.2 sans-serif;
166
    }
167

168
    .email-container h1,
169
    .email-container p {
170
      margin: 12px auto;
171
      max-width: 600px;
172
      padding: 0 24px;
173
    }
174

175
    .email-container a {
176
      color: #592acb;
177
      text-decoration: none;
178
    }
179

180
    .email-container table {
181
      table-layout: fixed;
182
    }
183

184
    .email-container .logo > td {
185
      height: 100px;
186
      background-color: #f9f9fa;
187
      background-position: 50%;
188
      background-image: url('${images.logoLight}');
189
      background-repeat: no-repeat;
190
      background-size: 240px 50px;
191
      width: 100%;
192
    }
193

194
    @media screen and (max-width:600px) {
195
      .email-container .header-image {
196
        display: none;
197
      }
198
    }
199

200
    @media (prefers-color-scheme: dark) {
201
      .email-container .logo > td {
202
        background-image: url('${images.logoDark}')
203
      }
204
    }
205

206
    .bg-blue-5 {
207
      background-color: #aaf2ff;
208
    }
209

210
    .bg-purple-5 {
211
      background-color: #e7dfff;
212
    }
213

214
    .bg-green-05 {
215
      background-color: #e3fff3;
216
    }
217

218
    .bg-violet-5 {
219
      background-color: #f7e2ff;
220
    }
221

222
    .bg-orange-5 {
223
      background-color: #fff4de;
224
    }
225

226
    .bg-yellow-5 {
227
      background-color: #ffc;
228
    }
229

230
    .bg-red-5 {
231
      background-color: #ffdfe7;
232
    }
233

234
    .bg-pink-5 {
235
      background-color: #ffdef0;
236
    }
237

238
    .breach-logo {
239
      display: block;
240
      height: 100%;
241
      width: 100%;
242
    }
243

244
    .breach-logo-email {
245
      border-radius: 50%;
246
      display: block;
247
      height: 100%;
248
      width: 100%;
249
    }
250
  </style>
251
`
252

253
const getEmailContent = (data, partial) => {
×
254
  return `
×
255
    <table
256
      border='0'
257
      class='email-container'
258
      cellpadding='0'
259
      cellspacing='0'
260
      role='presentation'
261
      style='${tableStyle}'
262
    >
263
      ${emailHeader({
264
        heading: data.heading,
265
        subhead: data.subheading ?? ''
×
266
      })}
267
      ${partial(data)}
268
      ${emailFooter(data)}
269
    </table>
270
  `
271
}
272

273
const getPreviewTemplate = (data, partial) => `
×
274
  ${getStyles()}
275
  ${getEmailContent(data, partial)}
276
`
277

278
const getTemplate = (data, partial) => {
×
279
  return `
×
280
    <!doctype html>
281
    <html>
282
      <head>
283
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
284
        <meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
285

286
        <title>
287
          ${getMessage('brand-fx-monitor')}
288
        </title>
289

290
        ${getStyles()}
291
      </head>
292

293
      <body class='email-body' style='${bodyStyle}'>
294
        ${getEmailContent(data, partial)}
295
      </body>
296
    </html>
297
  `
298
}
299

300
export { getPreviewTemplate, getTemplate }
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