@playstack/mail
Portable typed mail rendering, envelope validation, provider templates, and delivery contracts.
Free. MIT licensed. Check preview availability before installing. See package access.
@playstack/mail separates typed application rendering and validated portable envelopes from Resend, Postmark, Amazon SES, Mailgun, SendGrid, SMTP, or test delivery providers.
Install
After confirming preview access, install the package at your application's shared Playstack version:
npm install --save-exact @playstack/mail@0.1.0-beta.1Check the peer requirements below before choosing a runtime or provider.
Compose rendered mail
import { createMail } from '@playstack/mail'
import { createPostmarkMailProvider } from '@playstack/mail/postmark'
const mail = createMail({
driver: createPostmarkMailProvider(process.env.POSTMARK_SERVER_TOKEN!, {
messageStream: 'outbound',
}),
from: { address: 'hello@example.com', name: 'Example' },
replyTo: 'support@example.com',
})
await mail.send({
to: 'person@example.com',
template: renderInvitation,
props: { name: 'Ada', invitationUrl },
})The application owns rendering and localization. A typed template returns subject plus HTML/text before provider I/O, so renderer failures are distinct from delivery failures. Chakra Email or any other renderer can implement it; this package has no React dependency.
Driver configuration
createMail property | Required | Purpose |
|---|---|---|
driver | Yes | Portable provider bridge with native client and capabilities. |
from | Yes | Default address or { address, name }. |
replyTo | No | Default reply address, overridable per message. |
Messages accept to, cc, and bcc as one or many address inputs, optional sender/reply overrides, custom safe headers, and attachments with filename, MIME type, and byte/blob content. The total recipient ceiling is 50. Control/header injection and protected-header overrides fail before delivery.
Provider bridge and capabilities
A provider exposes its native client, declares whether provider templates are supported, and sends a normalized rendered or provider-template message. mail.client and mail.capabilities preserve those escape hatches.
- Postmark supports rendered mail and provider
templateId/exact-JSONmodel. Configure provider instances for the required message stream; an injected SDK client can be shared. - Resend supports rendered mail and provider templates. Template variables are limited to strings and numbers and are validated before provider I/O.
- SMTP supports rendered mail and exposes the Nodemailer transport/
verify; filesystem and URL attachment loading are disabled on the portable path. CaptureMailProviderfrom@playstack/mail/testingrecords normalized messages for tests.messages()returns the rendered/provider-template union;rawMessages()andtemplateMessages()return each side already narrowed, so a test can readsubjectandtextwithout checkingkindfirst.
import { CaptureMailProvider } from '@playstack/mail/testing'
const capture = new CaptureMailProvider()
const mail = createMail({ driver: capture, from: 'no-reply@example.com' })
await mail.send({ to: 'person@example.com', template: renderInvitation, props })
const [message] = capture.rawMessages()
expect(message?.text).toContain('/invitations/accept?token=')Results and failure policy
send returns provider message ID plus accepted/rejected recipients. Acceptance means submission, not inbox delivery. Input, unsupported-capability, and provider-rejection errors are non-retryable; rate-limit and definitive availability errors may be retried under application policy. MailOutcomeUnknownError means a send may already have succeeded, so reconcile before retrying.
SMTP success requires a nonempty message ID and explicit accepted/rejected arrays accounting for the requested recipients, with no foreign or conflicting evidence. An optional returned envelope must also match. Missing, malformed, mismatched, or partial evidence produces non-retryable uncertainty. SmtpReceiptError extends MailOutcomeUnknownError and exposes immutable indexed outcomes without recipient addresses. Complete explicit rejection is MailRejectedError; partial acceptance must never trigger a whole-message resend. A timeout or connection error can occur after submission and is not proof of rejection.
Postmark attribution
The Postmark provider options support tag and metadata, forwarded as native Tag and Metadata for rendered and provider-template sends. Create a per-attempt provider around the same injected SDK client when attribution varies; no client-method wrapper is required. Tags allow up to 1,000 characters. Metadata allows up to ten fields, with keys up to 20 characters and values up to 80; case-insensitive duplicate keys are rejected before I/O. Attribution does not provide delivery idempotency.
Boundary
The package submits immediately. Queueing, retry idempotency, provider webhooks, bounces, complaints, opens, and analytics belong to application workflows—typically @playstack/queues. Native provider-only attachments and administration remain available through mail.client.
API entry points and requirements
Reference snapshot: @playstack/mail@0.1.0-beta.1. Import only the entry point your runtime needs. Paths below are relative to the installed package; use Go to Definition in your editor to inspect exact parameters, return types and overloads. Do not import the declaration-file paths directly.
| Public entry point | Declaration file |
|---|---|
@playstack/mail | ./dist/index.d.ts |
@playstack/mail/errors | ./dist/errors.d.ts |
@playstack/mail/mailgun | ./dist/mailgun.d.ts |
@playstack/mail/types | ./dist/types.d.ts |
@playstack/mail/testing | ./dist/testing.d.ts |
@playstack/mail/postmark | ./dist/postmark.d.ts |
@playstack/mail/resend | ./dist/resend.d.ts |
@playstack/mail/ses | ./dist/ses.d.ts |
@playstack/mail/sendgrid | ./dist/sendgrid.d.ts |
@playstack/mail/smtp | ./dist/smtp.d.ts |
@playstack/mail/package.json | No TypeScript declaration (asset or metadata export). |
Node.js engine requirement: >=20. This is not a claim that every entry point works in browsers or Workers.
Peer dependencies
Keep existing framework versions that satisfy these ranges. Install optional peers only when using the corresponding adapter. The package manager resolves ordinary dependencies separately.
| Peer | Compatible range | When needed |
|---|---|---|
@aws-sdk/client-sesv2 | ^3.1119.0 | Optional; only for the entry points that use it. |
@sendgrid/mail | ^8.1.6 | Optional; only for the entry points that use it. |
mailgun.js | ^14.0.0 | Optional; only for the entry points that use it. |
nodemailer | ^9.0.5 | Optional; only for the entry points that use it. |
postmark | ^5.1.0 | Optional; only for the entry points that use it. |
resend | ^6.24.0 | Optional; only for the entry points that use it. |
For a complete first program, start with Getting started. For API lookup and partial-example conventions, see Reading the reference. Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.