eusend
Integrations

Payload CMS

Send Payload's verification, password-reset and application email through eusend with the official email adapter — a REST adapter that works on serverless, unlike SMTP.

Payload sends email through an email adapter: a small object in your payload.config.ts that every payload.sendEmail() call, and every built-in auth email, goes through. @eusend_dev/payload-email is the eusend adapter.

It calls the eusend REST API rather than SMTP, so it works in serverless functions — where holding an SMTP connection open is the usual reason @payloadcms/email-nodemailer misbehaves.

Installation

npm install @eusend_dev/payload-email
# or
bun add @eusend_dev/payload-email

Setup

Verify your sending domain

Add your domain under Domains and publish the DNS records — the adapter can only send from a verified domain. See domains if you have not done this yet.

Add the API key

Create a key under API keys and put it in your environment:

.env
EUSEND_API_KEY=eu_live_...

Configure the adapter

payload.config.ts
import { buildConfig } from 'payload'
import { eusendAdapter } from '@eusend_dev/payload-email'

export default buildConfig({
  email: eusendAdapter({
    apiKey: process.env.EUSEND_API_KEY || '',
    defaultFromAddress: 'hello@yourdomain.com',
    defaultFromName: 'Your app',
  }),
  // ...the rest of your config
})

Send

await payload.sendEmail({
  to: 'customer@example.com',
  subject: 'Your order is confirmed',
  html: '<p>Thanks for your order!</p>',
})

Payload's own auth emails — email verification and password resets — now go the same way, and every message appears in your email log.

Options

ParameterTypeDescription
apiKeyrequiredstringAn eusend API key.
defaultFromAddressrequiredstringSender used when a message sets no from. Its domain must be verified on your account.
defaultFromNamerequiredstringDisplay name paired with defaultFromAddress.
overrideRecipientAddressstringRedirect every message to this address instead of its real recipients. cc and bcc are dropped along with them, so a staging environment cannot copy a real customer.
tagsRecord<string, string>Tags added to every send, e.g. { source: "payload" }. They filter the email log and are returned on the webhook events for the send.
trackOpensbooleanOverride open tracking for messages sent through this adapter.
trackClicksbooleanOverride click tracking for messages sent through this adapter.
baseUrlstringOverride the API host. Only useful against a staging deployment.

Tagging Payload's email

Tags let you tell Payload's traffic apart from the rest of your sending in the email log, and route it in webhooks:

email: eusendAdapter({
  apiKey: process.env.EUSEND_API_KEY || '',
  defaultFromAddress: 'hello@yourdomain.com',
  defaultFromName: 'Your app',
  tags: { source: 'payload' },
})

Staging without mailing customers

overrideRecipientAddress sends everything to one inbox. Unlike the to-only overrides some adapters ship, cc and bcc are dropped with the real recipients — a redirected message that still copies the customer is not a redirected message.

email: eusendAdapter({
  apiKey: process.env.EUSEND_API_KEY || '',
  defaultFromAddress: 'hello@yourdomain.com',
  defaultFromName: 'Your app',
  overrideRecipientAddress: process.env.STAGING_INBOX,
})

Attachments

payload.sendEmail() takes nodemailer's attachment shape. All four forms work:

await payload.sendEmail({
  to: 'customer@example.com',
  subject: 'Your invoice',
  html: '<p>Attached.</p>',
  attachments: [
    { filename: 'invoice.pdf', content: pdfBuffer },
    { filename: 'notes.txt', content: 'plain text' },
    { path: 'https://example.com/invoice.pdf' },
    { path: '/var/data/invoice.pdf' },
  ],
})

A URL is fetched by eusend at send time; a filesystem path is read by the adapter before the request. A string content is treated as UTF-8 text unless you pass encoding: 'base64' — the same rule nodemailer applies, so an attachment ported over from @payloadcms/email-nodemailer arrives intact.

Set cid on an attachment to reference it from your HTML as <img src="cid:invoice-logo"> instead of showing it as a download.

Up to 20 attachments per message, 10 MB combined.

Errors

A failed send throws Payload's APIError, carrying the HTTP status and the eusend error code:

Error sending email: DOMAIN_NOT_VERIFIED - Domain not verified

The two you are most likely to meet first are DOMAIN_NOT_VERIFIED, when defaultFromAddress is on a domain that has not finished verifying, and ALL_SUPPRESSED, when every recipient is on your suppression list.

Coming from another adapter

Swapping @payloadcms/email-resend for eusend is the import, the factory name and the key:

payload.config.ts
- import { resendAdapter } from '@payloadcms/email-resend'
+ import { eusendAdapter } from '@eusend_dev/payload-email'

  export default buildConfig({
-   email: resendAdapter({
+   email: eusendAdapter({
      defaultFromAddress: 'hello@yourdomain.com',
      defaultFromName: 'Your app',
-     apiKey: process.env.RESEND_API_KEY || '',
+     apiKey: process.env.EUSEND_API_KEY || '',
    }),
  })

From @payloadcms/email-nodemailer, replace the whole transportOptions block with the three fields above — the defaultFromAddress and defaultFromName you already have keep their meaning, and your payload.sendEmail() calls do not change.

If you would rather keep nodemailer, eusend also speaks SMTP: see the nodemailer guide.