Wraps

Send through your own AWS SES account with wrapsMailer and @wraps.dev/email.

wrapsMailer sends through AWS SES from your own account, with @wraps.dev/email. It is the default adapter for BYO packages.

terminal
npm install @byo-oss/mailer @wraps.dev/email

Use @wraps.dev/email 0.15.1 or later (0.2.0 of the mailer requires it). Before 0.15.1, a message with headers (such as the In-Reply-To and References of a threaded reply) was sent with a non-ASCII subject as raw 8-bit text instead of an RFC 2047 encoded-word. The SDK builds a raw message whenever headers are set.

lib/mailer.ts
import { wrapsMailer } from "@byo-oss/mailer/wraps";

export const mailer = wrapsMailer({ from: "Acme Support <help@acme.com>" });

Options

Option Default
from required Sender address, verified in SES: "Acme <help@acme.com>".
client a WrapsEmail built from the options below A configured new WrapsEmail({ ... }), or any { send } with the same shape (a fake, in tests). When set, the options below are ignored and results have no messageId.
region, credentials, roleArn, … from the environment Any other @wraps.dev/email config (WrapsEmailConfig), used to build the default client on the first send.

If what you have is an SES v1 SESClient, wrap it: wrapsMailer({ from, client: new WrapsEmail({ client: sesClient }) }). For an SES v2 SESv2Client, use sesMailer.

Errors

The SDK throws Result
A validation error (malformed address, bad header), SES MessageRejected or InvalidParameterValue. PermanentMailError
Anything else: throttling, network errors, missing credentials, a sender that isn’t verified yet. Rethrown, so it retries once you fix the account

Headers SES owns (From, To, Subject, Reply-To, Message-ID, Date, …) are rejected with a PermanentMailError, and so are malformed headers. Use replyTo for the reply-to address. A non-ASCII display name in from is sent RFC 2047 encoded. See Delivery semantics.

Result

wrapsMailer returns { id, messageId }. id is the SES message id.

SES replaces any Message-ID header with its own (AWS: “If you provide a Message-ID header, Amazon SES overrides the header with its own value”). In us-east-1 that header is <id@email.amazonses.com>, and that is messageId. AWS doesn’t document the header’s host, so it comes from real sends, and only us-east-1 has been checked.

messageId is left out:

  • in any other region;
  • when the region isn’t known. It comes from the region option, else AWS_REGION or AWS_DEFAULT_REGION. A region set only in an AWS profile isn’t read, so set one of those;
  • when endpoint is set (LocalStack, a proxy), or when AWS_ENDPOINT_URL, AWS_ENDPOINT_URL_SES or AWS_ENDPOINT_URL_SESV2 is set, since the AWS SDK sends there instead of SES. An endpoint set only in an AWS profile isn’t read;
  • when you pass client, because its region can’t be read.

Setting AWS_IGNORE_CONFIGURED_ENDPOINT_URLS to exactly true makes the AWS SDK ignore AWS_ENDPOINT_URL, AWS_ENDPOINT_URL_SES and AWS_ENDPOINT_URL_SESV2, so those variables no longer leave messageId out. The AWS SDK for JavaScript reads this variable only from @smithy/core 3.29.6 (July 2026) on. An older SDK still sends to the endpoint variables, and the mailer would then return a messageId SES never wrote, so update the SDK before you set it. Any other value, such as 1 or TRUE, doesn’t count, and is ignored without an error. A custom endpoint set in code still leaves it out. A profile’s ignore_configured_endpoint_urls isn’t read, so an endpoint variable still leaves it out in that case.

If the client returns no message id after SES accepted the send, id is "ses:<uuid>" rather than a throw, which would send the message again. (@wraps.dev/email itself throws when SES returns no id, which retries the send.)

Edit this page on GitHub ↗@byo-oss/mailer 0.2.0