@strapi/provider-email-nodemailer 5.37.1 → 5.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +724 -11
- package/dist/index.d.ts +106 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +149 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +149 -22
- package/dist/index.mjs.map +1 -1
- package/dist/utils/email-address.d.ts +142 -0
- package/dist/utils/email-address.d.ts.map +1 -0
- package/dist/utils/index.d.ts +17 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/package.json +20 -7
package/README.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @strapi/provider-email-nodemailer
|
|
2
2
|
|
|
3
|
+
A feature-rich Nodemailer email provider for Strapi with support for DKIM, OAuth2, connection pooling, calendar invitations, newsletters, and more.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
| Category | Features |
|
|
8
|
+
| ------------------ | ------------------------------------------------------------------------------ |
|
|
9
|
+
| **Sending** | Priority, custom headers, attachments, embedded images, AMP4Email |
|
|
10
|
+
| **Security** | DKIM signing, OAuth2, requireTLS, file/URL access restrictions |
|
|
11
|
+
| **Performance** | Connection pooling, rate limiting |
|
|
12
|
+
| **Deliverability** | List-Unsubscribe headers (Gmail/Outlook), DSN bounce tracking, custom envelope |
|
|
13
|
+
| **Rich Content** | Calendar invitations (iCalendar), AMP4Email interactive emails |
|
|
14
|
+
| **Connectivity** | SOCKS/HTTP proxy support, NTLM and custom auth mechanisms |
|
|
15
|
+
| **Utilities** | RFC 5322/2047/6531 email address parsing and formatting |
|
|
16
|
+
|
|
3
17
|
## Resources
|
|
4
18
|
|
|
5
19
|
- [LICENSE](LICENSE)
|
|
@@ -127,17 +141,716 @@ await strapi.plugin('email').service('email').send({
|
|
|
127
141
|
|
|
128
142
|
The following fields are supported:
|
|
129
143
|
|
|
130
|
-
| Field
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
| cc
|
|
136
|
-
| bcc
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
144
|
+
| Field | Description |
|
|
145
|
+
| ------------------ | ----------------------------------------------------------------------------- |
|
|
146
|
+
| **Core** | |
|
|
147
|
+
| from | Email address of the sender |
|
|
148
|
+
| to | Comma separated list or an array of recipients |
|
|
149
|
+
| cc | Comma separated list or an array of recipients |
|
|
150
|
+
| bcc | Comma separated list or an array of recipients |
|
|
151
|
+
| replyTo | Email address to which replies are sent |
|
|
152
|
+
| sender | Address for the Sender: field (for "on behalf of" emails) |
|
|
153
|
+
| subject | Subject of the email |
|
|
154
|
+
| **Content** | |
|
|
155
|
+
| text | Plaintext version of the message |
|
|
156
|
+
| html | HTML version of the message |
|
|
157
|
+
| watchHtml | Apple Watch specific HTML version |
|
|
158
|
+
| amp | AMP4Email content for interactive emails |
|
|
159
|
+
| attachments | Array of attachment objects. See: https://nodemailer.com/message/attachments/ |
|
|
160
|
+
| alternatives | Array of alternative content (e.g. Markdown alongside HTML) |
|
|
161
|
+
| **Headers & Meta** | |
|
|
162
|
+
| headers | Custom SMTP headers object (e.g. `{ 'X-Custom': 'value' }`) |
|
|
163
|
+
| priority | Email priority: `'high'`, `'normal'`, or `'low'` |
|
|
164
|
+
| messageId | Custom Message-ID (random generated if not set) |
|
|
165
|
+
| date | Custom Date value (current UTC if not set) |
|
|
166
|
+
| xMailer | Control X-Mailer header (`false` to remove, string to override) |
|
|
167
|
+
| **Threading** | |
|
|
168
|
+
| inReplyTo | Message-ID of the email being replied to |
|
|
169
|
+
| references | Message-ID list this email references (array or space-separated string) |
|
|
170
|
+
| **Encoding** | |
|
|
171
|
+
| textEncoding | Force content-transfer-encoding: `'quoted-printable'` or `'base64'` |
|
|
172
|
+
| encoding | Encoding for the message content |
|
|
173
|
+
| normalizeHeaderKey | Function to normalize header key casing |
|
|
174
|
+
| **Advanced** | |
|
|
175
|
+
| icalEvent | Calendar event invitation (iCalendar format) |
|
|
176
|
+
| list | RFC 2369 List-\* headers - enables one-click unsubscribe in Gmail/Outlook |
|
|
177
|
+
| envelope | Custom SMTP envelope for bounce handling (MAIL FROM / RCPT TO) |
|
|
178
|
+
| dkim | Per-message DKIM signing options (overrides transport-level DKIM) |
|
|
179
|
+
| attachDataUrls | Convert `data:` URIs in HTML to embedded CID attachments (`true`/`false`) |
|
|
180
|
+
| dsn | Delivery Status Notification - request bounce/success reports |
|
|
181
|
+
| auth | Per-message OAuth2 credentials for multi-user sending |
|
|
182
|
+
| **Security** | |
|
|
183
|
+
| disableUrlAccess | Fail if content tries to load from a URL (`true`/`false`) |
|
|
184
|
+
| disableFileAccess | Fail if content tries to load from a file path (`true`/`false`) |
|
|
185
|
+
| **Raw MIME** | |
|
|
186
|
+
| raw | Pre-built MIME message (skips message generation, envelope must be set) |
|
|
187
|
+
|
|
188
|
+
Every field listed above is explicitly allowlisted. Unknown properties are silently dropped to prevent injection.
|
|
189
|
+
|
|
190
|
+
### Sending with priority and custom headers
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
await strapi
|
|
194
|
+
.plugin('email')
|
|
195
|
+
.service('email')
|
|
196
|
+
.send({
|
|
197
|
+
to: 'someone@example.com',
|
|
198
|
+
subject: 'Urgent',
|
|
199
|
+
text: 'Please respond ASAP',
|
|
200
|
+
priority: 'high',
|
|
201
|
+
headers: {
|
|
202
|
+
'X-Custom-Header': 'my-value',
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Calendar invitations
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
await strapi
|
|
211
|
+
.plugin('email')
|
|
212
|
+
.service('email')
|
|
213
|
+
.send({
|
|
214
|
+
to: 'someone@example.com',
|
|
215
|
+
subject: 'Meeting Invitation',
|
|
216
|
+
text: 'You are invited to a meeting',
|
|
217
|
+
icalEvent: {
|
|
218
|
+
method: 'REQUEST',
|
|
219
|
+
content: `BEGIN:VCALENDAR
|
|
220
|
+
VERSION:2.0
|
|
221
|
+
BEGIN:VEVENT
|
|
222
|
+
DTSTART:20260130T100000Z
|
|
223
|
+
DTEND:20260130T110000Z
|
|
224
|
+
SUMMARY:Team Meeting
|
|
225
|
+
END:VEVENT
|
|
226
|
+
END:VCALENDAR`,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Newsletter with List-Unsubscribe
|
|
232
|
+
|
|
233
|
+
When sending newsletters, include List-Unsubscribe headers so Gmail and Outlook show a one-click "Unsubscribe" button:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
await strapi
|
|
237
|
+
.plugin('email')
|
|
238
|
+
.service('email')
|
|
239
|
+
.send({
|
|
240
|
+
to: 'subscriber@example.com',
|
|
241
|
+
subject: 'Weekly Newsletter',
|
|
242
|
+
html: '<h1>This week in tech...</h1>',
|
|
243
|
+
list: {
|
|
244
|
+
unsubscribe: {
|
|
245
|
+
url: 'https://example.com/unsubscribe?id=123',
|
|
246
|
+
comment: 'Unsubscribe',
|
|
247
|
+
},
|
|
248
|
+
help: 'support@example.com?subject=help',
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Delivery Status Notifications (DSN)
|
|
254
|
+
|
|
255
|
+
Request bounce reports or delivery confirmations:
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
await strapi
|
|
259
|
+
.plugin('email')
|
|
260
|
+
.service('email')
|
|
261
|
+
.send({
|
|
262
|
+
to: 'someone@example.com',
|
|
263
|
+
subject: 'Important document',
|
|
264
|
+
text: 'Please confirm receipt',
|
|
265
|
+
dsn: {
|
|
266
|
+
id: 'msg-unique-123',
|
|
267
|
+
return: 'headers',
|
|
268
|
+
notify: ['success', 'failure'],
|
|
269
|
+
recipient: 'bounce-handler@example.com',
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Custom SMTP Envelope (Bounce Handling)
|
|
275
|
+
|
|
276
|
+
Control the MAIL FROM address independently from the visible From header, useful for tracking bounces:
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
await strapi
|
|
280
|
+
.plugin('email')
|
|
281
|
+
.service('email')
|
|
282
|
+
.send({
|
|
283
|
+
from: 'Newsletter <newsletter@example.com>',
|
|
284
|
+
to: 'subscriber@example.com',
|
|
285
|
+
subject: 'Newsletter',
|
|
286
|
+
text: 'Hello!',
|
|
287
|
+
envelope: {
|
|
288
|
+
from: 'bounce+subscriber=example.com@example.com',
|
|
289
|
+
to: 'subscriber@example.com',
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### AMP4Email (Interactive Emails)
|
|
295
|
+
|
|
296
|
+
Send interactive AMP-powered emails (supported by Gmail):
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
await strapi
|
|
300
|
+
.plugin('email')
|
|
301
|
+
.service('email')
|
|
302
|
+
.send({
|
|
303
|
+
to: 'someone@example.com',
|
|
304
|
+
subject: 'Interactive Email',
|
|
305
|
+
text: 'Fallback for non-AMP clients',
|
|
306
|
+
html: '<p>Fallback for non-AMP clients</p>',
|
|
307
|
+
amp: `<!doctype html>
|
|
308
|
+
<html ⚡4email>
|
|
309
|
+
<head>
|
|
310
|
+
<meta charset="utf-8">
|
|
311
|
+
<style amp4email-boilerplate>body{visibility:hidden}</style>
|
|
312
|
+
<script async src="https://cdn.ampproject.org/v0.js"></script>
|
|
313
|
+
</head>
|
|
314
|
+
<body>
|
|
315
|
+
<p>This is an interactive AMP email!</p>
|
|
316
|
+
</body>
|
|
317
|
+
</html>`,
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Email threading (replies & conversations)
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
await strapi
|
|
325
|
+
.plugin('email')
|
|
326
|
+
.service('email')
|
|
327
|
+
.send({
|
|
328
|
+
to: 'someone@example.com',
|
|
329
|
+
subject: 'Re: Project Update',
|
|
330
|
+
text: 'Thanks for the update!',
|
|
331
|
+
inReplyTo: '<original-msg-id@example.com>',
|
|
332
|
+
references: ['<original-msg-id@example.com>', '<prev-msg-id@example.com>'],
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Sending on behalf of (Sender field)
|
|
337
|
+
|
|
338
|
+
```js
|
|
339
|
+
await strapi.plugin('email').service('email').send({
|
|
340
|
+
from: 'CEO <ceo@example.com>',
|
|
341
|
+
sender: 'assistant@example.com',
|
|
342
|
+
to: 'board@example.com',
|
|
343
|
+
subject: 'Quarterly Report',
|
|
344
|
+
text: 'Please find the report attached',
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Per-message DKIM signing
|
|
349
|
+
|
|
350
|
+
Override transport-level DKIM settings for a specific message:
|
|
351
|
+
|
|
352
|
+
```js
|
|
353
|
+
await strapi
|
|
354
|
+
.plugin('email')
|
|
355
|
+
.service('email')
|
|
356
|
+
.send({
|
|
357
|
+
to: 'someone@example.com',
|
|
358
|
+
subject: 'Signed Email',
|
|
359
|
+
text: 'This email has a specific DKIM signature',
|
|
360
|
+
dkim: {
|
|
361
|
+
domainName: 'special.example.com',
|
|
362
|
+
keySelector: 'mail2026',
|
|
363
|
+
privateKey: process.env.DKIM_PRIVATE_KEY_SPECIAL,
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Alternative content formats
|
|
369
|
+
|
|
370
|
+
Provide multiple representations of the same content (email clients pick the best match):
|
|
371
|
+
|
|
372
|
+
```js
|
|
373
|
+
await strapi
|
|
374
|
+
.plugin('email')
|
|
375
|
+
.service('email')
|
|
376
|
+
.send({
|
|
377
|
+
to: 'someone@example.com',
|
|
378
|
+
subject: 'Multi-format Email',
|
|
379
|
+
text: 'Plain text version',
|
|
380
|
+
html: '<p>HTML version</p>',
|
|
381
|
+
alternatives: [
|
|
382
|
+
{
|
|
383
|
+
contentType: 'text/x-web-markdown',
|
|
384
|
+
content: '**Markdown** version',
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Raw MIME passthrough
|
|
391
|
+
|
|
392
|
+
Send a pre-built MIME message directly (skips all message generation):
|
|
393
|
+
|
|
394
|
+
```js
|
|
395
|
+
await strapi
|
|
396
|
+
.plugin('email')
|
|
397
|
+
.service('email')
|
|
398
|
+
.send({
|
|
399
|
+
to: 'someone@example.com',
|
|
400
|
+
subject: 'ignored',
|
|
401
|
+
text: 'ignored',
|
|
402
|
+
envelope: {
|
|
403
|
+
from: 'sender@example.com',
|
|
404
|
+
to: 'someone@example.com',
|
|
405
|
+
},
|
|
406
|
+
raw: 'From: sender@example.com\r\nTo: someone@example.com\r\nSubject: Raw\r\n\r\nPre-built body',
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Embedded images
|
|
411
|
+
|
|
412
|
+
```js
|
|
413
|
+
await strapi
|
|
414
|
+
.plugin('email')
|
|
415
|
+
.service('email')
|
|
416
|
+
.send({
|
|
417
|
+
to: 'someone@example.com',
|
|
418
|
+
subject: 'Newsletter',
|
|
419
|
+
html: '<p>Logo: <img src="cid:logo@company"/></p>',
|
|
420
|
+
attachments: [
|
|
421
|
+
{
|
|
422
|
+
filename: 'logo.png',
|
|
423
|
+
path: '/path/to/logo.png',
|
|
424
|
+
cid: 'logo@company',
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## Advanced configuration
|
|
431
|
+
|
|
432
|
+
### OAuth2 authentication
|
|
433
|
+
|
|
434
|
+
For services like Gmail or Outlook, you can use OAuth2 instead of passwords:
|
|
435
|
+
|
|
436
|
+
```js
|
|
437
|
+
module.exports = ({ env }) => ({
|
|
438
|
+
email: {
|
|
439
|
+
config: {
|
|
440
|
+
provider: 'nodemailer',
|
|
441
|
+
providerOptions: {
|
|
442
|
+
host: 'smtp.gmail.com',
|
|
443
|
+
port: 465,
|
|
444
|
+
secure: true,
|
|
445
|
+
auth: {
|
|
446
|
+
type: 'OAuth2',
|
|
447
|
+
user: env('SMTP_USER'),
|
|
448
|
+
clientId: env('OAUTH_CLIENT_ID'),
|
|
449
|
+
clientSecret: env('OAUTH_CLIENT_SECRET'),
|
|
450
|
+
refreshToken: env('OAUTH_REFRESH_TOKEN'),
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
settings: {
|
|
454
|
+
defaultFrom: env('SMTP_USER'),
|
|
455
|
+
defaultReplyTo: env('SMTP_USER'),
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
#### Per-message OAuth2 (multi-user)
|
|
463
|
+
|
|
464
|
+
You can send emails on behalf of different users through a single transporter. Configure the transporter with shared OAuth2 credentials, then pass user-specific tokens per message:
|
|
465
|
+
|
|
466
|
+
```js
|
|
467
|
+
// config/plugins.js - shared transporter with OAuth2
|
|
468
|
+
module.exports = ({ env }) => ({
|
|
469
|
+
email: {
|
|
470
|
+
config: {
|
|
471
|
+
provider: 'nodemailer',
|
|
472
|
+
providerOptions: {
|
|
473
|
+
host: 'smtp.gmail.com',
|
|
474
|
+
port: 465,
|
|
475
|
+
secure: true,
|
|
476
|
+
auth: {
|
|
477
|
+
type: 'OAuth2',
|
|
478
|
+
clientId: env('OAUTH_CLIENT_ID'),
|
|
479
|
+
clientSecret: env('OAUTH_CLIENT_SECRET'),
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
settings: {
|
|
483
|
+
defaultFrom: 'noreply@example.com',
|
|
484
|
+
defaultReplyTo: 'support@example.com',
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
```js
|
|
492
|
+
// Send as a specific user
|
|
493
|
+
await strapi
|
|
494
|
+
.plugin('email')
|
|
495
|
+
.service('email')
|
|
496
|
+
.send({
|
|
497
|
+
to: 'recipient@example.com',
|
|
498
|
+
subject: 'Hello from user',
|
|
499
|
+
text: 'Sent on behalf of a specific user',
|
|
500
|
+
auth: {
|
|
501
|
+
user: 'specific-user@gmail.com',
|
|
502
|
+
refreshToken: userRefreshToken,
|
|
503
|
+
accessToken: userAccessToken,
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
See [nodemailer OAuth2 documentation](https://nodemailer.com/smtp/oauth2/) for details.
|
|
509
|
+
|
|
510
|
+
### Connection pooling
|
|
511
|
+
|
|
512
|
+
For better performance when sending multiple emails:
|
|
513
|
+
|
|
514
|
+
```js
|
|
515
|
+
module.exports = ({ env }) => ({
|
|
516
|
+
email: {
|
|
517
|
+
config: {
|
|
518
|
+
provider: 'nodemailer',
|
|
519
|
+
providerOptions: {
|
|
520
|
+
host: env('SMTP_HOST'),
|
|
521
|
+
port: 465,
|
|
522
|
+
secure: true,
|
|
523
|
+
pool: true,
|
|
524
|
+
maxConnections: 5,
|
|
525
|
+
maxMessages: 100,
|
|
526
|
+
auth: {
|
|
527
|
+
user: env('SMTP_USERNAME'),
|
|
528
|
+
pass: env('SMTP_PASSWORD'),
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
settings: {
|
|
532
|
+
defaultFrom: 'hello@example.com',
|
|
533
|
+
defaultReplyTo: 'hello@example.com',
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### DKIM signing
|
|
541
|
+
|
|
542
|
+
Add DKIM signatures to improve email deliverability:
|
|
543
|
+
|
|
544
|
+
```js
|
|
545
|
+
module.exports = ({ env }) => ({
|
|
546
|
+
email: {
|
|
547
|
+
config: {
|
|
548
|
+
provider: 'nodemailer',
|
|
549
|
+
providerOptions: {
|
|
550
|
+
host: env('SMTP_HOST'),
|
|
551
|
+
port: 587,
|
|
552
|
+
auth: {
|
|
553
|
+
user: env('SMTP_USERNAME'),
|
|
554
|
+
pass: env('SMTP_PASSWORD'),
|
|
555
|
+
},
|
|
556
|
+
dkim: {
|
|
557
|
+
domainName: 'example.com',
|
|
558
|
+
keySelector: 'mail',
|
|
559
|
+
privateKey: env('DKIM_PRIVATE_KEY'),
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
settings: {
|
|
563
|
+
defaultFrom: 'hello@example.com',
|
|
564
|
+
defaultReplyTo: 'hello@example.com',
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Rate limiting
|
|
572
|
+
|
|
573
|
+
Limit the number of emails sent per time interval to avoid being flagged as spam:
|
|
574
|
+
|
|
575
|
+
```js
|
|
576
|
+
module.exports = ({ env }) => ({
|
|
577
|
+
email: {
|
|
578
|
+
config: {
|
|
579
|
+
provider: 'nodemailer',
|
|
580
|
+
providerOptions: {
|
|
581
|
+
host: env('SMTP_HOST'),
|
|
582
|
+
port: 465,
|
|
583
|
+
secure: true,
|
|
584
|
+
pool: true,
|
|
585
|
+
maxConnections: 5,
|
|
586
|
+
maxMessages: 100,
|
|
587
|
+
rateDelta: 1000, // Time interval in ms (1 second)
|
|
588
|
+
rateLimit: 5, // Max messages per rateDelta interval
|
|
589
|
+
auth: {
|
|
590
|
+
user: env('SMTP_USERNAME'),
|
|
591
|
+
pass: env('SMTP_PASSWORD'),
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
settings: {
|
|
595
|
+
defaultFrom: 'hello@example.com',
|
|
596
|
+
defaultReplyTo: 'hello@example.com',
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
});
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### Proxy support
|
|
604
|
+
|
|
605
|
+
Route SMTP connections through a SOCKS or HTTP proxy:
|
|
606
|
+
|
|
607
|
+
```js
|
|
608
|
+
module.exports = ({ env }) => ({
|
|
609
|
+
email: {
|
|
610
|
+
config: {
|
|
611
|
+
provider: 'nodemailer',
|
|
612
|
+
providerOptions: {
|
|
613
|
+
host: env('SMTP_HOST'),
|
|
614
|
+
port: 465,
|
|
615
|
+
secure: true,
|
|
616
|
+
proxy: env('SMTP_PROXY', 'socks5://127.0.0.1:1080'), // or 'http://proxy:3128'
|
|
617
|
+
auth: {
|
|
618
|
+
user: env('SMTP_USERNAME'),
|
|
619
|
+
pass: env('SMTP_PASSWORD'),
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
settings: {
|
|
623
|
+
defaultFrom: 'hello@example.com',
|
|
624
|
+
defaultReplyTo: 'hello@example.com',
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
For SOCKS proxy, install the `socks` package: `yarn add socks`.
|
|
632
|
+
|
|
633
|
+
### Require TLS
|
|
634
|
+
|
|
635
|
+
Force TLS encryption and refuse to send if the server doesn't support it:
|
|
636
|
+
|
|
637
|
+
```js
|
|
638
|
+
providerOptions: {
|
|
639
|
+
host: env('SMTP_HOST'),
|
|
640
|
+
port: 587,
|
|
641
|
+
requireTLS: true, // Fail if STARTTLS is not available
|
|
642
|
+
auth: { user: env('SMTP_USERNAME'), pass: env('SMTP_PASSWORD') },
|
|
643
|
+
},
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### Security options
|
|
647
|
+
|
|
648
|
+
Restrict file and URL access at the transport level (applies to all messages):
|
|
649
|
+
|
|
650
|
+
```js
|
|
651
|
+
module.exports = ({ env }) => ({
|
|
652
|
+
email: {
|
|
653
|
+
config: {
|
|
654
|
+
provider: 'nodemailer',
|
|
655
|
+
providerOptions: {
|
|
656
|
+
host: env('SMTP_HOST'),
|
|
657
|
+
port: 587,
|
|
658
|
+
auth: {
|
|
659
|
+
user: env('SMTP_USERNAME'),
|
|
660
|
+
pass: env('SMTP_PASSWORD'),
|
|
661
|
+
},
|
|
662
|
+
disableFileAccess: true,
|
|
663
|
+
disableUrlAccess: true,
|
|
664
|
+
},
|
|
665
|
+
settings: {
|
|
666
|
+
defaultFrom: 'hello@example.com',
|
|
667
|
+
defaultReplyTo: 'hello@example.com',
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
You can also set these per message when processing content from untrusted sources:
|
|
675
|
+
|
|
676
|
+
```js
|
|
677
|
+
await strapi.plugin('email').service('email').send({
|
|
678
|
+
to: 'someone@example.com',
|
|
679
|
+
subject: 'User-generated content',
|
|
680
|
+
html: userProvidedHtml,
|
|
681
|
+
disableUrlAccess: true,
|
|
682
|
+
disableFileAccess: true,
|
|
683
|
+
});
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
**Security note:** This provider uses an explicit allowlist for all message fields. Unknown or unsupported properties are silently dropped, preventing property injection attacks.
|
|
687
|
+
|
|
688
|
+
## Provider methods
|
|
689
|
+
|
|
690
|
+
### verify
|
|
691
|
+
|
|
692
|
+
Verify your SMTP configuration without sending an email:
|
|
693
|
+
|
|
694
|
+
```js
|
|
695
|
+
const emailProvider = strapi.plugin('email').provider;
|
|
696
|
+
|
|
697
|
+
try {
|
|
698
|
+
await emailProvider.verify();
|
|
699
|
+
console.log('SMTP connection is working');
|
|
700
|
+
} catch (error) {
|
|
701
|
+
console.error('SMTP configuration error:', error.message);
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
This tests DNS resolution, TCP connection, TLS upgrade (if applicable), and authentication.
|
|
706
|
+
|
|
707
|
+
### isIdle
|
|
708
|
+
|
|
709
|
+
Check if the transporter has available capacity (useful with connection pooling):
|
|
710
|
+
|
|
711
|
+
```js
|
|
712
|
+
if (emailProvider.isIdle()) {
|
|
713
|
+
// Safe to send more emails
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### close
|
|
718
|
+
|
|
719
|
+
Close all connections gracefully (recommended when using connection pooling):
|
|
720
|
+
|
|
721
|
+
```js
|
|
722
|
+
// On application shutdown
|
|
723
|
+
emailProvider.close();
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
## Email Address Utilities
|
|
727
|
+
|
|
728
|
+
This package includes RFC-compliant utilities for parsing and formatting email addresses.
|
|
729
|
+
|
|
730
|
+
### Import
|
|
731
|
+
|
|
732
|
+
```js
|
|
733
|
+
import {
|
|
734
|
+
parseEmailAddress,
|
|
735
|
+
formatEmailAddress,
|
|
736
|
+
parseMultipleEmailAddresses,
|
|
737
|
+
isValidEmail,
|
|
738
|
+
decodeRfc2047,
|
|
739
|
+
encodeRfc2047Base64,
|
|
740
|
+
} from '@strapi/provider-email-nodemailer/utils';
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Parsing Email Addresses
|
|
744
|
+
|
|
745
|
+
Parse email addresses in various RFC 5322 formats:
|
|
746
|
+
|
|
747
|
+
```js
|
|
748
|
+
// Simple email
|
|
749
|
+
parseEmailAddress('test@example.com');
|
|
750
|
+
// { name: null, email: 'test@example.com', original: '...' }
|
|
751
|
+
|
|
752
|
+
// Name with angle brackets
|
|
753
|
+
parseEmailAddress('John Doe <john@example.com>');
|
|
754
|
+
// { name: 'John Doe', email: 'john@example.com', original: '...' }
|
|
755
|
+
|
|
756
|
+
// Quoted name (RFC 5322)
|
|
757
|
+
parseEmailAddress('"Doe, John" <john@example.com>');
|
|
758
|
+
// { name: 'Doe, John', email: 'john@example.com', original: '...' }
|
|
759
|
+
|
|
760
|
+
// RFC 2047 encoded name (non-ASCII characters)
|
|
761
|
+
parseEmailAddress('=?UTF-8?B?TcO8bGxlcg==?= <mueller@example.com>');
|
|
762
|
+
// { name: 'Müller', email: 'mueller@example.com', original: '...' }
|
|
763
|
+
|
|
764
|
+
// Comment format (RFC 5322)
|
|
765
|
+
parseEmailAddress('support@example.com (Support Team)');
|
|
766
|
+
// { name: 'Support Team', email: 'support@example.com', original: '...' }
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
### Formatting Email Addresses
|
|
770
|
+
|
|
771
|
+
Create properly formatted email address strings:
|
|
772
|
+
|
|
773
|
+
```js
|
|
774
|
+
// Simple format
|
|
775
|
+
formatEmailAddress('John Doe', 'john@example.com');
|
|
776
|
+
// 'John Doe <john@example.com>'
|
|
777
|
+
|
|
778
|
+
// Auto-quotes special characters
|
|
779
|
+
formatEmailAddress('Doe, John', 'john@example.com');
|
|
780
|
+
// '"Doe, John" <john@example.com>'
|
|
781
|
+
|
|
782
|
+
// Auto-encodes non-ASCII characters (RFC 2047)
|
|
783
|
+
formatEmailAddress('Müller', 'mueller@example.com');
|
|
784
|
+
// '=?UTF-8?B?TcO8bGxlcg==?= <mueller@example.com>'
|
|
785
|
+
|
|
786
|
+
// Skip encoding if needed
|
|
787
|
+
formatEmailAddress('Müller', 'mueller@example.com', { encodeNonAscii: false });
|
|
788
|
+
// 'Müller <mueller@example.com>'
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Multiple Addresses
|
|
792
|
+
|
|
793
|
+
Parse comma-separated email addresses (handles quoted strings with commas):
|
|
794
|
+
|
|
795
|
+
```js
|
|
796
|
+
parseMultipleEmailAddresses('a@example.com, "Doe, John" <b@example.com>');
|
|
797
|
+
// [
|
|
798
|
+
// { name: null, email: 'a@example.com', ... },
|
|
799
|
+
// { name: 'Doe, John', email: 'b@example.com', ... }
|
|
800
|
+
// ]
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### RFC 2047 Encoding/Decoding
|
|
804
|
+
|
|
805
|
+
Handle MIME encoded-words for non-ASCII characters:
|
|
806
|
+
|
|
807
|
+
```js
|
|
808
|
+
// Decode Base64 or Quoted-Printable
|
|
809
|
+
decodeRfc2047('=?UTF-8?B?U3RyYXBp?=');
|
|
810
|
+
// 'Strapi'
|
|
811
|
+
|
|
812
|
+
decodeRfc2047('=?UTF-8?Q?M=C3=BCller?=');
|
|
813
|
+
// 'Müller'
|
|
814
|
+
|
|
815
|
+
// Encode for MIME headers
|
|
816
|
+
encodeRfc2047Base64('Müller');
|
|
817
|
+
// '=?UTF-8?B?TcO8bGxlcg==?='
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### Validation
|
|
821
|
+
|
|
822
|
+
```js
|
|
823
|
+
isValidEmail('user@example.com'); // true
|
|
824
|
+
isValidEmail('invalid'); // false
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
### Normalization
|
|
828
|
+
|
|
829
|
+
All email addresses are automatically normalized to lowercase when parsed or formatted (per RFC 5321 section 2.4):
|
|
830
|
+
|
|
831
|
+
```js
|
|
832
|
+
parseEmailAddress('User@Example.COM');
|
|
833
|
+
// { name: null, email: 'user@example.com', original: 'User@Example.COM' }
|
|
834
|
+
|
|
835
|
+
formatEmailAddress('Admin', 'Admin@EXAMPLE.ORG');
|
|
836
|
+
// 'Admin <admin@example.org>'
|
|
837
|
+
|
|
838
|
+
normalizeEmail('Mixed.Case@Example.COM');
|
|
839
|
+
// 'mixed.case@example.com'
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### Supported RFC Standards
|
|
843
|
+
|
|
844
|
+
| RFC | Description |
|
|
845
|
+
| -------- | ---------------------------------------------------------------- |
|
|
846
|
+
| RFC 5321 | SMTP protocol - email address case-insensitive normalization |
|
|
847
|
+
| RFC 5322 | Internet Message Format (name <email>, quoted strings, comments) |
|
|
848
|
+
| RFC 2047 | MIME encoded-words (=?charset?encoding?text?=) |
|
|
849
|
+
| RFC 2369 | List-\* headers (List-Unsubscribe, List-Help, etc.) |
|
|
850
|
+
| RFC 3461 | Delivery Status Notifications (DSN) |
|
|
851
|
+
| RFC 6376 | DomainKeys Identified Mail (DKIM) signatures |
|
|
852
|
+
| RFC 6531 | Internationalized Email (UTF-8 in local parts) |
|
|
853
|
+
| RFC 6532 | Internationalized Email Headers (UTF-8 in header fields) |
|
|
141
854
|
|
|
142
855
|
## Troubleshooting
|
|
143
856
|
|