obsidian/wiki/payloadcms/email.md
2026-05-15 15:22:27 +01:00

5.4 KiB

title aliases tags sources created updated
Email — Adapters, SMTP, Resend, Attachments
payload-email
payloadcms-email
nodemailer-payload
payloadcms
email
nodemailer
resend
smtp
attachments
raw/email__overview.md
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/SendGridtransportOptions object or pre-built transport; any Nodemailer transport works
  • Dev shortcutnodemailerAdapter() 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 email config, but you can use additional transports manually inside hooks
  • Auth emails — password reset, email verification use same adapter; see wiki/payloadcms/authentication-email


Sources