5.4 KiB
5.4 KiB
| title | aliases | tags | sources | created | updated | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Email — Adapters, SMTP, Resend, Attachments |
|
|
|
2026-05-15 | 2026-05-15 |
Overview
Payload uses an adapter pattern for email. Pass an adapter into email property of buildConfig. Without it, Payload logs a warning on startup and on every sendEmail call.
Two official adapters:
| Adapter | Package | Best for |
|---|---|---|
| Nodemailer | @payloadcms/email-nodemailer |
SMTP, SendGrid, any Nodemailer transport; easy v2→v3 migration |
| Resend | @payloadcms/email-resend |
Vercel / serverless — much lighter than Nodemailer |
Both require at minimum: defaultFromAddress + defaultFromName.
Nodemailer Adapter
SMTP via transportOptions
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@example.com',
defaultFromName: 'My App',
transportOptions: {
host: process.env.SMTP_HOST,
port: 587,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
},
}),
})
Pre-created transport via transport
import nodemailer from 'nodemailer'
email: nodemailerAdapter({
defaultFromAddress: 'info@example.com',
defaultFromName: 'My App',
transport: nodemailer.createTransport({ /* ... */ }),
})
SendGrid custom transport
import nodemailerSendgrid from 'nodemailer-sendgrid'
email: nodemailerAdapter({
defaultFromAddress: 'info@example.com',
defaultFromName: 'My App',
transportOptions: nodemailerSendgrid({ apiKey: process.env.SENDGRID_API_KEY }),
})
Dev mode (no config)
Calling nodemailerAdapter() with no args uses ethereal.email — logs credentials to console. Safe for local development.
email: nodemailerAdapter()
Resend Adapter
Preferred for Vercel / serverless. Requires only an API key.
import { resendAdapter } from '@payloadcms/email-resend'
email: resendAdapter({
defaultFromAddress: 'dev@example.com',
defaultFromName: 'My App',
apiKey: process.env.RESEND_API_KEY || '',
})
Sending Email
Available anywhere payload instance is accessible:
await payload.sendEmail({
to: 'user@example.com',
subject: 'Hello',
html: '<p>Body</p>', // or: text: 'Body'
})
Attachments
Nodemailer — file path or Buffer
await payload.sendEmail({
to: 'user@example.com',
subject: 'Report',
html: '<p>See attached.</p>',
attachments: [
{ filename: 'invoice.pdf', path: '/var/data/invoice.pdf', contentType: 'application/pdf' },
{ filename: 'report.csv', content: Buffer.from('col1,col2\nA,B\n'), contentType: 'text/csv' },
],
})
Supports anything Nodemailer supports: streams, Buffers, URLs, inline CID images.
Resend — remote URL or Base64
// Remote URL (Resend fetches it)
attachments: [{ path: 'https://example.com/invoice.pdf', filename: 'invoice.pdf' }]
// Local file — must be Base64
import { readFile } from 'node:fs/promises'
const pdf = await readFile('/var/data/invoice.pdf')
attachments: [{ filename: 'invoice.pdf', content: pdf.toString('base64') }]
Attaching Files from Payload Media Collections
Local storage + Nodemailer
const doc = await payload.findByID({ collection: 'media', id })
await payload.sendEmail({
attachments: [{ filename: doc.filename, path: doc.url, contentType: doc.mimeType }],
})
Cloud storage (S3/Azure/GCS) + Nodemailer
const response = await fetch(doc.url)
const buffer = Buffer.from(await response.arrayBuffer())
attachments: [{ filename: doc.filename, content: buffer, contentType: doc.mimeType }]
Resend + cloud storage
// Resend fetches the URL directly — no download needed
attachments: [{ filename: doc.filename, path: doc.url }]
Resend + local storage
import { readFile } from 'node:fs/promises'
const buf = await readFile(doc.url)
attachments: [{ filename: doc.filename, content: buf.toString('base64') }]
Key Takeaways
- Adapter pattern — swap Nodemailer ↔ Resend without changing send-call code
- Resend for serverless — significantly lighter bundle than Nodemailer; use on Vercel
- Nodemailer for SMTP/SendGrid —
transportOptionsobject or pre-builttransport; any Nodemailer transport works - Dev shortcut —
nodemailerAdapter()with no args → ethereal.email auto-config - Attachments differ by adapter: Nodemailer accepts file paths/Buffers/streams; Resend expects Base64 for local files, URL for remote
- Multiple providers — Payload only supports one
emailconfig, but you can use additional transports manually inside hooks - Auth emails — password reset, email verification use same adapter; see wiki/payloadcms/authentication-email
Related
- wiki/payloadcms/authentication-email
- wiki/payloadcms/hooks — trigger custom emails via collection hooks
- wiki/payloadcms/upload — attaching media collection files to emails
- wiki/payloadcms/configuration —
buildConfigtop-level options
Sources
raw/email__overview.md— compiled 2026-05-15- https://payloadcms.com/docs/email/overview