Provider-agnostic email delivery with a small, result-based contract.
Use AWS SES v2, Resend, SMTP, the in-memory test provider, or an application-owned transport without coupling message workflows to one vendor.
Why Email · Quick start · Providers · Boundaries
@goobits/email standardizes message validation, sender defaults, sequential
batch delivery, verification, failure classification, and privacy-aware logging
while leaving credentials and provider lifecycle with the application.
Provider dependencies are optional peers. Consumers pay for only the transport they import, and custom providers need none of the bundled vendor SDKs.
The declared package contract requires Node.js 22 or newer. Other runtimes need a compatible toolchain and provider implementation and are not promised by the package metadata.
pnpm add @goobits/emailimport{createEmailService,createMockProvider}from'@goobits/email'constprovider=createMockProvider()constemail=createEmailService({
provider,from: 'no-reply@example.com',})constresult=awaitemail.send({to: 'person@example.com',subject: 'Welcome',text: 'Hello.',})if(!result.success){console.error(result.reason)}The package publishes TypeScript source entrypoints directly; consumers need a toolchain that can compile them.
| Import | Required peer | Responsibility |
|---|---|---|
@goobits/email | none | Service, contracts, and in-memory mock |
@goobits/email/ses | @aws-sdk/client-sesv2 | AWS SES v2 adapter |
@goobits/email/resend | resend | Resend adapter |
@goobits/email/smtp | nodemailer | SMTP adapter |
The application creates and owns each SDK client or SMTP transporter, including credentials, region, retries, TLS, pooling, and shutdown.
createEmailService requires a provider and default from address. Each
message needs at least one of html or text. The service accepts multiple
recipients, reply-to, CC, BCC, attachments, inline CIDs, and custom headers.
Transport support for inline attachments and custom headers varies by provider.
EmailProvider.send() is required to resolve with an EmailResult. The
built-in providers translate transport failures into unsuccessful results; a
custom provider that rejects violates the interface and that rejection
propagates through EmailService.send(). Failure reasons form a closed set:
configuration-missing, invalid-recipient, transport-error,
rate-limited, or unknown. Batch sends are sequential and return one result
per recipient.
When a logger is supplied, the package records operational categories and provider identifiers without recipient addresses, subjects, bodies, or raw provider errors. The application still owns redaction of any context it adds.
The package does not render templates, queue or schedule messages, or process bounces and complaints. Pass pre-rendered HTML or text, wrap delivery in an application queue when needed, and handle provider webhooks separately.
pnpm install --frozen-lockfile
pnpm typecheck
pnpm test