├── .github
└── pull_request_template.md
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── renovate.json
├── src
├── plugin.js
└── replaceBase64Images.js
└── test
└── replaceBase64ImagesSpec.js
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | > **Note** - Since this is a public repository, make sure that we're not publishing private data in the code, commit comments, or this PR.
4 |
5 | > **Note for reviewers** - Please add a 2nd reviewer if the PR affects more than 15 files or 100 lines (not counting
6 | `package-lock.json`), if it incurs significant risk, or if it is going through a 2nd review+fix cycle.
7 |
8 | ## 📚 Context/Description Behind The Change
9 |
18 |
19 | ## 🚨 Potential Risks & What To Monitor After Deployment
20 |
28 |
29 | ## 🧑🔬 How Has This Been Tested?
30 |
36 |
37 | ## 🚚 Release Plan
38 |
44 |
45 |
46 |
47 |
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Changelog
2 |
3 | * 2.1.1 Fixed issue where other types of quotes not supported (thanks @thorn0)
4 |
5 | * 2.1.0 Adds support for rewriting CSS images, eg `
`
6 |
7 | * 2.0.0 **Breaking change**: the constructor now needs to be called as a function. Adds `cidPrefix` option.
8 |
9 | * 1.0.1 Support image types other than `png`
10 |
11 | * 1.0.0 Initial release
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Mixmax, Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nodemailer plugin for handling inline Base64 images as attachments
2 |
3 | This plugin will convert base64-encoded images in your [nodemailer](https://github.com/nodemailer/nodemailer) email to be inline ("CID-referenced") attachments within the email. Inline attachments are useful because them embed the image inside the actual email, so it's viewable even if the user is checking their email without an internet connection. But if you're OK with requiring that the user be online to see the image, then consider hosting your images from AWS Cloudfront using [nodemailer-base64-to-s3](https://github.com/crocodilejs/nodemailer-base64-to-s3).
4 |
5 | Base64 images are generally a [bad idea](https://sendgrid.com/blog/embedding-images-emails-facts/) because they aren't supported in most email clients. This Nodemailer plugin will take base64 images in your email html in the form:
6 |
7 |
8 |
9 | and replace it with a CID-referenced attachment that works in all email clients.
10 |
11 | ## Install
12 |
13 | ```
14 | npm install nodemailer-plugin-inline-base64
15 | ```
16 | or
17 | ```
18 | npm install nodemailer-plugin-inline-base64 --save
19 | ```
20 |
21 | ## Usage
22 |
23 | #### 1. Load the `nodemailer-plugin-inline-base64` plugin:
24 |
25 | ```javascript
26 | var inlineBase64 = require('nodemailer-plugin-inline-base64');
27 | ```
28 |
29 | #### 2. Attach it as a 'compile' handler for a nodemailer transport object
30 |
31 | ```javascript
32 | nodemailerTransport.use('compile', inlineBase64(options))
33 | ```
34 | Options allow to set CID prefix1 ```{cidPrefix: 'somePrefix_'}```,
35 | then all inline images will have prefix in cid, i.e.: `cid:somePrefix_5fe3b631c651bdb1`. If you don't need this,
36 | you can use inlineBase64 plugin without options.
37 |
38 |
39 |
40 | ## Example
41 |
42 | ```javascript
43 | var nodemailer = require('nodemailer');
44 | var inlineBase64 = require('nodemailer-plugin-inline-base64');
45 | transporter.use('compile', inlineBase64({cidPrefix: 'somePrefix_'}));
46 | transporter.sendMail({
47 | from: 'me@example.com',
48 | to: 'hello@mixmax.com',
49 | html: '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAACCAYAAACE7KJkAAAAI0lEQVRYR+3DMQ0AAAgDsKlFzZxgEhOcbdIEAIBf7Y6qqn8P0MMQZPno7TMAAAAASUVORK5CYII=">'
50 | });
51 | ```
52 |
53 | ## References
54 | 1 It might be useful for reply email processing, example with [MailParser](https://github.com/andris9/mailparser)
55 |
56 | ```javascript
57 | mp.on("attachment", function(attachment, mail){
58 | if (!attachment.contentId.includes('somePrefix')) { // process only images attached by user in reply
59 | // ...
60 | }
61 | });
62 | ```
63 |
64 | ## License
65 |
66 | **MIT**
67 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodemailer-plugin-inline-base64",
3 | "version": "2.1.1",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "version": "2.1.1",
9 | "license": "MIT",
10 | "devDependencies": {
11 | "jasmine-node": "^1.14.5"
12 | }
13 | },
14 | "node_modules/coffee-script": {
15 | "version": "1.12.7",
16 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz",
17 | "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==",
18 | "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)",
19 | "dev": true,
20 | "bin": {
21 | "cake": "bin/cake",
22 | "coffee": "bin/coffee"
23 | },
24 | "engines": {
25 | "node": ">=0.8.0"
26 | }
27 | },
28 | "node_modules/fileset": {
29 | "version": "0.1.8",
30 | "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz",
31 | "integrity": "sha1-UGuRqTluqn4y+0KoQHfHoMc2t0E=",
32 | "dev": true,
33 | "dependencies": {
34 | "glob": "3.x",
35 | "minimatch": "0.x"
36 | }
37 | },
38 | "node_modules/gaze": {
39 | "version": "0.3.4",
40 | "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.3.4.tgz",
41 | "integrity": "sha1-X5S92gr+U7xxCWm81vKCVI1gwnk=",
42 | "dev": true,
43 | "dependencies": {
44 | "fileset": "~0.1.5",
45 | "minimatch": "~0.2.9"
46 | },
47 | "engines": {
48 | "node": ">= 0.6.0"
49 | }
50 | },
51 | "node_modules/glob": {
52 | "version": "3.2.11",
53 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
54 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=",
55 | "dev": true,
56 | "dependencies": {
57 | "inherits": "2",
58 | "minimatch": "0.3"
59 | },
60 | "engines": {
61 | "node": "*"
62 | }
63 | },
64 | "node_modules/glob/node_modules/minimatch": {
65 | "version": "0.3.0",
66 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
67 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=",
68 | "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue",
69 | "dev": true,
70 | "dependencies": {
71 | "lru-cache": "2",
72 | "sigmund": "~1.0.0"
73 | },
74 | "engines": {
75 | "node": "*"
76 | }
77 | },
78 | "node_modules/growl": {
79 | "version": "1.7.0",
80 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz",
81 | "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=",
82 | "dev": true
83 | },
84 | "node_modules/inherits": {
85 | "version": "2.0.3",
86 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
87 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
88 | "dev": true
89 | },
90 | "node_modules/jasmine-growl-reporter": {
91 | "version": "0.0.3",
92 | "resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.0.3.tgz",
93 | "integrity": "sha1-uHrlUeNZ0orVIXdl6u9sB7dj9sg=",
94 | "dev": true,
95 | "dependencies": {
96 | "growl": "~1.7.0"
97 | }
98 | },
99 | "node_modules/jasmine-node": {
100 | "version": "1.14.5",
101 | "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-1.14.5.tgz",
102 | "integrity": "sha1-GOg5e4VpJO53ADZmw3MbWupQw50=",
103 | "deprecated": "jasmine-node 1.x & 2.x are deprecated, with known vulnerability in jasmine-growl-reporter pre-2.0.0",
104 | "dev": true,
105 | "dependencies": {
106 | "coffee-script": ">=1.0.1",
107 | "gaze": "~0.3.2",
108 | "jasmine-growl-reporter": "~0.0.2",
109 | "jasmine-reporters": "~1.0.0",
110 | "mkdirp": "~0.3.5",
111 | "requirejs": ">=0.27.1",
112 | "underscore": ">= 1.3.1",
113 | "walkdir": ">= 0.0.1"
114 | },
115 | "bin": {
116 | "jasmine-node": "bin/jasmine-node"
117 | }
118 | },
119 | "node_modules/jasmine-reporters": {
120 | "version": "1.0.2",
121 | "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-1.0.2.tgz",
122 | "integrity": "sha1-q2E+1Zd9x0h+hbPBL2qOqNsq3jE=",
123 | "dev": true,
124 | "dependencies": {
125 | "mkdirp": "~0.3.5"
126 | }
127 | },
128 | "node_modules/lru-cache": {
129 | "version": "2.7.3",
130 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
131 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=",
132 | "dev": true
133 | },
134 | "node_modules/minimatch": {
135 | "version": "0.2.14",
136 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
137 | "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=",
138 | "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue",
139 | "dev": true,
140 | "dependencies": {
141 | "lru-cache": "2",
142 | "sigmund": "~1.0.0"
143 | },
144 | "engines": {
145 | "node": "*"
146 | }
147 | },
148 | "node_modules/mkdirp": {
149 | "version": "0.3.5",
150 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
151 | "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=",
152 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
153 | "dev": true
154 | },
155 | "node_modules/requirejs": {
156 | "version": "2.3.5",
157 | "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.5.tgz",
158 | "integrity": "sha512-svnO+aNcR/an9Dpi44C7KSAy5fFGLtmPbaaCeQaklUz8BQhS64tWWIIlvEA5jrWICzlO/X9KSzSeXFnZdBu8nw==",
159 | "dev": true,
160 | "bin": {
161 | "r_js": "bin/r.js",
162 | "r.js": "bin/r.js"
163 | },
164 | "engines": {
165 | "node": ">=0.4.0"
166 | }
167 | },
168 | "node_modules/sigmund": {
169 | "version": "1.0.1",
170 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
171 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
172 | "dev": true
173 | },
174 | "node_modules/underscore": {
175 | "version": "1.9.0",
176 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz",
177 | "integrity": "sha512-4IV1DSSxC1QK48j9ONFK1MoIAKKkbE8i7u55w2R6IqBqbT7A/iG7aZBCR2Bi8piF0Uz+i/MG1aeqLwl/5vqF+A==",
178 | "dev": true
179 | },
180 | "node_modules/walkdir": {
181 | "version": "0.0.12",
182 | "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.12.tgz",
183 | "integrity": "sha512-HFhaD4mMWPzFSqhpyDG48KDdrjfn409YQuVW7ckZYhW4sE87mYtWifdB/+73RA7+p4s4K18n5Jfx1kHthE1gBw==",
184 | "dev": true,
185 | "engines": {
186 | "node": ">=0.6.0"
187 | }
188 | }
189 | },
190 | "dependencies": {
191 | "coffee-script": {
192 | "version": "1.12.7",
193 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz",
194 | "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==",
195 | "dev": true
196 | },
197 | "fileset": {
198 | "version": "0.1.8",
199 | "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz",
200 | "integrity": "sha1-UGuRqTluqn4y+0KoQHfHoMc2t0E=",
201 | "dev": true,
202 | "requires": {
203 | "glob": "3.x",
204 | "minimatch": "0.x"
205 | }
206 | },
207 | "gaze": {
208 | "version": "0.3.4",
209 | "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.3.4.tgz",
210 | "integrity": "sha1-X5S92gr+U7xxCWm81vKCVI1gwnk=",
211 | "dev": true,
212 | "requires": {
213 | "fileset": "~0.1.5",
214 | "minimatch": "~0.2.9"
215 | }
216 | },
217 | "glob": {
218 | "version": "3.2.11",
219 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
220 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=",
221 | "dev": true,
222 | "requires": {
223 | "inherits": "2",
224 | "minimatch": "0.3"
225 | },
226 | "dependencies": {
227 | "minimatch": {
228 | "version": "0.3.0",
229 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
230 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=",
231 | "dev": true,
232 | "requires": {
233 | "lru-cache": "2",
234 | "sigmund": "~1.0.0"
235 | }
236 | }
237 | }
238 | },
239 | "growl": {
240 | "version": "1.7.0",
241 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz",
242 | "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=",
243 | "dev": true
244 | },
245 | "inherits": {
246 | "version": "2.0.3",
247 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
248 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
249 | "dev": true
250 | },
251 | "jasmine-growl-reporter": {
252 | "version": "0.0.3",
253 | "resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.0.3.tgz",
254 | "integrity": "sha1-uHrlUeNZ0orVIXdl6u9sB7dj9sg=",
255 | "dev": true,
256 | "requires": {
257 | "growl": "~1.7.0"
258 | }
259 | },
260 | "jasmine-node": {
261 | "version": "1.14.5",
262 | "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-1.14.5.tgz",
263 | "integrity": "sha1-GOg5e4VpJO53ADZmw3MbWupQw50=",
264 | "dev": true,
265 | "requires": {
266 | "coffee-script": ">=1.0.1",
267 | "gaze": "~0.3.2",
268 | "jasmine-growl-reporter": "~0.0.2",
269 | "jasmine-reporters": "~1.0.0",
270 | "mkdirp": "~0.3.5",
271 | "requirejs": ">=0.27.1",
272 | "underscore": ">= 1.3.1",
273 | "walkdir": ">= 0.0.1"
274 | }
275 | },
276 | "jasmine-reporters": {
277 | "version": "1.0.2",
278 | "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-1.0.2.tgz",
279 | "integrity": "sha1-q2E+1Zd9x0h+hbPBL2qOqNsq3jE=",
280 | "dev": true,
281 | "requires": {
282 | "mkdirp": "~0.3.5"
283 | }
284 | },
285 | "lru-cache": {
286 | "version": "2.7.3",
287 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
288 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=",
289 | "dev": true
290 | },
291 | "minimatch": {
292 | "version": "0.2.14",
293 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
294 | "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=",
295 | "dev": true,
296 | "requires": {
297 | "lru-cache": "2",
298 | "sigmund": "~1.0.0"
299 | }
300 | },
301 | "mkdirp": {
302 | "version": "0.3.5",
303 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
304 | "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=",
305 | "dev": true
306 | },
307 | "requirejs": {
308 | "version": "2.3.5",
309 | "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.5.tgz",
310 | "integrity": "sha512-svnO+aNcR/an9Dpi44C7KSAy5fFGLtmPbaaCeQaklUz8BQhS64tWWIIlvEA5jrWICzlO/X9KSzSeXFnZdBu8nw==",
311 | "dev": true
312 | },
313 | "sigmund": {
314 | "version": "1.0.1",
315 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
316 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
317 | "dev": true
318 | },
319 | "underscore": {
320 | "version": "1.9.0",
321 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz",
322 | "integrity": "sha512-4IV1DSSxC1QK48j9ONFK1MoIAKKkbE8i7u55w2R6IqBqbT7A/iG7aZBCR2Bi8piF0Uz+i/MG1aeqLwl/5vqF+A==",
323 | "dev": true
324 | },
325 | "walkdir": {
326 | "version": "0.0.12",
327 | "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.12.tgz",
328 | "integrity": "sha512-HFhaD4mMWPzFSqhpyDG48KDdrjfn409YQuVW7ckZYhW4sE87mYtWifdB/+73RA7+p4s4K18n5Jfx1kHthE1gBw==",
329 | "dev": true
330 | }
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodemailer-plugin-inline-base64",
3 | "version": "2.1.1",
4 | "description": "Nodemailer plugin that will inline base64 images",
5 | "main": "src/plugin.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "dependencies": {},
10 | "devDependencies": {
11 | "jasmine-node": "^1.14.5"
12 | },
13 | "scripts": {
14 | "test": "jasmine-node test/"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git://github.com/mixmaxhq/nodemailer-plugin-inline-base64.git"
19 | },
20 | "keywords": [
21 | "nodemailer",
22 | "plugin",
23 | "base64",
24 | "attachment"
25 | ],
26 | "author": "Brad Vogel (https://mixmax.com/)",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/mixmaxhq/nodemailer-plugin-inline-base64/issues"
30 | },
31 | "homepage": "https://github.com/mixmaxhq/nodemailer-plugin-inline-base64"
32 | }
33 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "local>mixmaxhq/renovate-config"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/src/plugin.js:
--------------------------------------------------------------------------------
1 | var replaceBase64Images = require('./replaceBase64Images');
2 | var crypto = require('crypto');
3 |
4 | var plugin = function(options) {
5 | options = options || {};
6 | return function(mail, done) {
7 | if (!mail || !mail.data || !mail.data.html) {
8 | return done();
9 | }
10 |
11 | mail.resolveContent(mail.data, 'html', function(err, html) {
12 | if (err) return done(err);
13 |
14 | var attachments = {};
15 |
16 | html = replaceBase64Images(html, function(mimeType, base64) {
17 | if (attachments[base64]) return attachments[base64].cid;
18 |
19 | var randomCid = (options.cidPrefix || '') + crypto.randomBytes(8).toString('hex');
20 |
21 | attachments[base64] = {
22 | contentType: mimeType,
23 | cid: randomCid,
24 | content: base64,
25 | encoding: 'base64',
26 | contentDisposition: 'inline'
27 | };
28 | return randomCid;
29 | });
30 |
31 | mail.data.html = html;
32 |
33 | if (!mail.data.attachments) mail.data.attachments = [];
34 |
35 | Object.keys(attachments).forEach(function(cid) {
36 | mail.data.attachments.push(attachments[cid]);
37 | });
38 |
39 | done();
40 | });
41 | }
42 | };
43 |
44 | module.exports = plugin;
45 |
--------------------------------------------------------------------------------
/src/replaceBase64Images.js:
--------------------------------------------------------------------------------
1 | var replaceBase64Images = function(html, getCid) {
2 | //Replace html base64 images
3 | var result = html.replace(/(
)/g, function(g, start, mimeType, base64, end) {
4 | return start + 'cid:' + getCid(mimeType, base64) + end;
5 | });
6 |
7 | //Replace css base64 images
8 | return result.replace(
9 | /(url\(\s*('|"|"|"|'|[49];|[xX]2[27];)?)data:(image\/(?:png|jpe?g|gif));base64,([\s\S]*?)(\2\s*\))/g,
10 | function(g, start, quot, mimeType, base64, end) {
11 | return start + 'cid:' + getCid(mimeType, base64) + end;
12 | }
13 | );
14 | };
15 |
16 | module.exports = replaceBase64Images;
17 |
--------------------------------------------------------------------------------
/test/replaceBase64ImagesSpec.js:
--------------------------------------------------------------------------------
1 | var replaceBase64Images = require('../src/replaceBase64Images');
2 |
3 | describe('replaceBase64Images', function() {
4 | var replacer = function(mimeType, contents) {
5 | // Make up a fake string to very parameters were passed correctly.
6 | return mimeType + '-' + contents.split('').reverse().join('');
7 | };
8 |
9 | describe('HTML
tags', function() {
10 |
11 | it('should replace a single source (double quote)', function() {
12 | var html = '
';
13 | var expected = '
';
14 | expect(replaceBase64Images(html, replacer)).toBe(expected);
15 | });
16 |
17 | it('should replace a single source (single quote)', function() {
18 | var html = '
';
19 | var expected = '
';
20 | expect(replaceBase64Images(html, replacer)).toBe(expected);
21 | });
22 |
23 | it('should handle gifs', function() {
24 | var html = '
';
25 | var expected = '
';
26 | expect(replaceBase64Images(html, replacer)).toBe(expected);
27 | });
28 |
29 | it('should handle jpegs', function() {
30 | var html = '
';
31 | var expected = '
';
32 | expect(replaceBase64Images(html, replacer)).toBe(expected);
33 | });
34 |
35 | it('should handle attributes on the HTML tag', function() {
36 | var html = '
';
37 | var expected = '
';
38 | expect(replaceBase64Images(html, replacer)).toBe(expected);
39 | });
40 |
41 | it('should replace multiple sources', function() {
42 | var html = '
';
43 | var expected = '
';
44 | expect(replaceBase64Images(html, replacer)).toBe(expected);
45 | });
46 |
47 | it('should ignore other mimetypes', function() {
48 | var html = '
';
49 | var expected = '
';
50 | expect(replaceBase64Images(html, replacer)).toBe(expected);
51 | });
52 |
53 | it('should not touch regular img tags', function() {
54 | var html = '
';
55 | expect(replaceBase64Images(html, replacer)).toBe(html);
56 | });
57 |
58 | it('should not replace base64 outside of an image tag', function() {
59 | var html = 'data:image/png;base64,abc';
60 | expect(replaceBase64Images(html, replacer)).toBe(html);
61 | });
62 |
63 | });
64 |
65 | describe('Inline CSS `url`', function() {
66 |
67 | it('should replace a single source (no quote)', function() {
68 | var html = '';
69 | var expected = '';
70 | expect(replaceBase64Images(html, replacer)).toBe(expected);
71 | });
72 |
73 | it('should replace a single source (double quote)', function() {
74 | var html = '';
75 | var expected = '';
76 | expect(replaceBase64Images(html, replacer)).toBe(expected);
77 | });
78 |
79 | it('should replace a single source (single quote)', function() {
80 | var html = '';
81 | var expected = '';
82 | expect(replaceBase64Images(html, replacer)).toBe(expected);
83 | });
84 |
85 | '" " " " "'.split(' ').forEach(function(q) {
86 | it('should replace a single source (double quote escaped as ' + q + ')', function() {
87 | var html = '';
88 | var expected = '';
89 | expect(replaceBase64Images(html, replacer)).toBe(expected);
90 | });
91 | });
92 |
93 | '' ' ' ''.split(' ').forEach(function(q) {
94 | it('should replace a single source (single quote escaped as ' + q + ')', function() {
95 | var html = '';
96 | var expected = '';
97 | expect(replaceBase64Images(html, replacer)).toBe(expected);
98 | });
99 | });
100 |
101 | it('should handle gifs', function() {
102 | var html = '';
103 | var expected = '';
104 | expect(replaceBase64Images(html, replacer)).toBe(expected);
105 | });
106 |
107 | it('should handle jpegs', function() {
108 | var html = '';
109 | var expected = '';
110 | expect(replaceBase64Images(html, replacer)).toBe(expected);
111 | });
112 |
113 | it('should replace multiple sources', function() {
114 | var html = '
';
115 | var expected = '
';
116 | expect(replaceBase64Images(html, replacer)).toBe(expected);
117 | });
118 |
119 | it('should ignore other mimetypes', function() {
120 | var html = '';
121 | var expected = '';
122 | expect(replaceBase64Images(html, replacer)).toBe(expected);
123 | });
124 |
125 | it('should not replace base64 outside of a css `url()`', function() {
126 | var html = 'data:image/png;base64,abc';
127 | expect(replaceBase64Images(html, replacer)).toBe(html);
128 | });
129 |
130 | it('should allow spaces like `url( "data:..." )`', function() {
131 | var html = '';
132 | var expected = '';
133 | expect(replaceBase64Images(html, replacer)).toBe(expected);
134 | });
135 |
136 | });
137 |
138 | });
139 |
--------------------------------------------------------------------------------