1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316---
title: Email Functionality
label: Overview
order: 10
desc: Payload uses an adapter pattern to enable email functionality. Set up email functions such as password resets, order confirmations and more.
keywords: email, overview, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
## Introduction
Payload has a few email adapters that can be imported to enable email functionality. The [@payloadcms/email-nodemailer](https://www.npmjs.com/package/@payloadcms/email-nodemailer) package will be the package most will want to install. This package provides an easy way to use [Nodemailer](https://nodemailer.com) for email and won't get in your way for those already familiar.
The email adapter should be passed into the `email` property of the Payload Config. This will allow Payload to send [auth-related emails](../authentication/email) for things like password resets, new user verification, and any other email sending needs you may have.
## Configuration
### Default Configuration
When email is not needed or desired, Payload will log a warning on startup notifying that email is not configured. A warning message will also be logged on any attempt to send an email.
### Email Adapter
An email adapter will require at least the following fields:
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| **`defaultFromName`** \* | The name part of the From field that will be seen on the delivered email |
| **`defaultFromAddress`** \* | The email address part of the From field that will be used when delivering email |
### Official Email Adapters
| Name | Package | Description |
| ---------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Nodemailer | [@payloadcms/email-nodemailer](https://www.npmjs.com/package/@payloadcms/email-nodemailer) | Use any [Nodemailer transport](https://nodemailer.com/transports), including SMTP, Resend, SendGrid, and more. This was provided by default in Payload 2.x. This is the easiest migration path. |
| Resend | [@payloadcms/email-resend](https://www.npmjs.com/package/@payloadcms/email-resend) | Resend email via their REST API. This is preferred for serverless platforms such as Vercel because it is much more lightweight than the nodemailer adapter. |
## Nodemailer Configuration
| Option | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`transport`** | The Nodemailer transport object for when you want to do it yourself, not needed when transportOptions is set |
| **`transportOptions`** | An object that configures the transporter that Payload will create. For all the available options see the [Nodemailer documentation](https://nodemailer.com) or see the examples below |
## Use SMTP
Simple Mail Transfer Protocol (SMTP) options can be passed in using the `transportOptions` object on the `email` options. See the [Nodemailer SMTP documentation](https://nodemailer.com/smtp/) for more information, including details on when `secure` should and should not be set to `true`.
**Example email options using SMTP:**
```ts
import { buildConfig } from 'payload'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@payloadcms.com',
defaultFromName: 'Payload',
// Nodemailer transportOptions
transportOptions: {
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
},
}),
})
```
**Example email options using nodemailer.createTransport:**
```ts
import { buildConfig } from 'payload'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
import nodemailer from 'nodemailer'
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@payloadcms.com',
defaultFromName: 'Payload',
// Any Nodemailer transport can be used
transport: nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
}),
}),
})
```
**Custom Transport:**
You also have the ability to bring your own nodemailer transport. This is an example of using the SendGrid nodemailer transport.
```ts
import { buildConfig } from 'payload'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
import nodemailerSendgrid from 'nodemailer-sendgrid'
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@payloadcms.com',
defaultFromName: 'Payload',
transportOptions: nodemailerSendgrid({
apiKey: process.env.SENDGRID_API_KEY,
}),
}),
})
```
During development, if you pass nothing to `nodemailerAdapter`, it will use the [ethereal.email](https://ethereal.email) service.
This will log the ethereal.email details to console on startup.
```ts
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
export default buildConfig({
email: nodemailerAdapter(),
})
```
## Resend Configuration
The Resend adapter requires an API key to be passed in the options. This can be found in the Resend dashboard. This is the preferred package if you are deploying on Vercel because this is much more lightweight than the Nodemailer adapter.
| Option | Description |
| ------ | ----------------------------------- |
| apiKey | The API key for the Resend service. |
```ts
import { buildConfig } from 'payload'
import { resendAdapter } from '@payloadcms/email-resend'
export default buildConfig({
email: resendAdapter({
defaultFromAddress: 'dev@payloadcms.com',
defaultFromName: 'Payload CMS',
apiKey: process.env.RESEND_API_KEY || '',
}),
})
```
## Sending Mail
With a working transport you can call it anywhere you have access to Payload by calling `payload.sendEmail(message)`. The `message` will contain the `to`, `subject` and `html` or `text` for the email being sent. Other options are also available and can be seen in the sendEmail args. Support for these will depend on the adapter being used.
```ts
// Example of sending an email
const email = await payload.sendEmail({
to: 'test@example.com',
subject: 'This is a test email',
text: 'This is my message body',
})
```
## Sending email with attachments
**Nodemailer adapter (SMTP/SendGrid/etc.)**
Works with `@payloadcms/email-nodemailer` and any Nodemailer transport.
```ts
await payload.sendEmail({
to: 'user@example.com',
subject: 'Your report',
html: '<p>See attached.</p>',
attachments: [
// From a file path (local disk, mounted volume, etc.)
{
filename: 'invoice.pdf',
path: '/var/data/invoice.pdf',
contentType: 'application/pdf',
},
// From a Buffer you generated at runtime
{
filename: 'report.csv',
content: Buffer.from('col1,col2\nA,B\n'),
contentType: 'text/csv',
},
],
})
```
Anything supported by Nodemailer’s attachments—streams, Buffers, URLs, content IDs for inline images (cid), etc.—will work here.
### Resend adapter
Works with @payloadcms/email-resend.
For attachments from remote URLs
```ts
await payload.sendEmail({
to: 'user@example.com',
subject: 'Your invoice',
html: '<p>Thanks! Invoice attached.</p>',
attachments: [
{
// Resend will fetch this URL
path: 'https://example.com/invoices/1234.pdf',
filename: 'invoice-1234.pdf',
},
],
})
```
For a local file
```ts
import { readFile } from 'node:fs/promises'
const pdf = await readFile('/var/data/invoice.pdf')
await payload.sendEmail({
to: 'user@example.com',
subject: 'Your invoice',
html: '<p>Thanks! Invoice attached.</p>',
attachments: [
{
filename: 'invoice.pdf',
// Resend expects Base64 here
content: pdf.toString('base64'),
},
],
})
```
## Attaching files from Payload media collections
If you store files in a Payload collection with `upload: true`, you can attach them to emails by fetching the document and using its file data.
**Example: Attaching a file from a Media collection**
```ts
const mediaDoc = await payload.findByID({
collection: 'media',
id: 'your-file-id',
})
// For local storage adapter
await payload.sendEmail({
to: 'user@example.com',
subject: 'Your document',
html: '<p>Attached is the document you requested.</p>',
attachments: [
{
filename: mediaDoc.filename,
path: mediaDoc.url, // Local file path when using local storage
contentType: mediaDoc.mimeType,
},
],
})
// For cloud storage (S3, Azure, GCS, etc.)
const response = await fetch(mediaDoc.url)
const buffer = Buffer.from(await response.arrayBuffer())
await payload.sendEmail({
to: 'user@example.com',
subject: 'Your document',
html: '<p>Attached is the document you requested.</p>',
attachments: [
{
filename: mediaDoc.filename,
content: buffer,
contentType: mediaDoc.mimeType,
},
],
})
```
### With Resend adapter:
```ts
const mediaDoc = await payload.findByID({
collection: 'media',
id: 'your-file-id',
})
// If using cloud storage, Resend can fetch from the URL directly
await payload.sendEmail({
to: 'user@example.com',
subject: 'Your document',
html: '<p>Attached is the document you requested.</p>',
attachments: [
{
filename: mediaDoc.filename,
path: mediaDoc.url, // Resend will fetch from this URL
},
],
})
// For local storage, read the file and convert to Base64
import { readFile } from 'node:fs/promises'
const fileBuffer = await readFile(mediaDoc.url)
await payload.sendEmail({
to: 'user@example.com',
subject: 'Your document',
html: '<p>Attached is the document you requested.</p>',
attachments: [
{
filename: mediaDoc.filename,
content: fileBuffer.toString('base64'),
},
],
})
```
## Using multiple mail providers
Payload supports the use of a single transporter of email, but there is nothing stopping you from having more. Consider a use case where sending bulk email is handled differently than transactional email and could be done using a [hook](/docs/hooks/overview).