New:Socket for Asana Is Now Available.Learn more
Get Started

@ultimat3/mail

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ultimat3/mail

Transactional email as data: one template renders HTML and text, sent through a job.

latest
Source
npmnpm
Version
19.0.0
Version published
Maintainers
1
Created
Source

@ultimat3/mail ✉️

Transactional email as data. One template renders both an HTML part and a plain-text part, every string is an i18n key, every colour is a design token, and delivery is a job.

import { defineMail, send, blocks, t } from '@ultimat3/mail';

export const receiptMail = defineMail({
  id: 'receipt',
  subject: 'mail.receipt.subject',            // an i18n key, never a literal
  input: t.object({ name: t.string, url: t.url }),
  template: ({ data }) => [
    blocks.heading('mail.receipt.heading', { name: data.name }),
    blocks.paragraph('mail.receipt.body'),
    blocks.button('mail.receipt.cta', data.url),
  ],
});

await send(receiptMail, { name: user.name, url }, { to: user.email, locale: ctx.locale });

send validates data through the mail's schema, renders, then enqueues mail.send. It delivers inline only when { sync: true } is passed or no job driver is configured.

Rules

RuleWhy
locale is required by the typea mail is read hours later; there is no ambient request locale to fall back to. X_MAIL_LOCALE_MISSING is the backstop for JS callers
Text part is mandatoryHTML-only mail scores as spam and is unreadable to screen readers. Empty text ⇒ X_MAIL_TEXT_MISSING
Text is derived from blocksnever scraped out of the HTML, so the two parts cannot drift
Every string is a keymail.<id>.<slot>; English lives in src/catalog.ts and app catalogs override it
Every colour is a tokenMAIL_TOKENS in layout.ts holds light + dark hexes; templates never see a hex
Every date takes an IANA zoneoptions.tz, else ctx.tz, else UTC
No CR/LF in a header-bound fieldchecked in renderMessage and again in sendMailJob, so every driver refuses the same message (X_MAIL_HEADER_INVALID). mime.ts keeps its own gate for the headers the SMTP transport mints itself
Sending is a jobretry: { attempts: 5, backoff: 'exponential' }, idempotency key derived from (mailId, recipients, hash(rendered)), or (mailId, your key) when you pass one — a caller's key is scoped to its mail so two templates cannot dedupe each other away

Drivers

setMailDriver(driver) once at boot. Asking for one that was never set is X_MAIL_DRIVER_UNAVAILABLE.

DriverUseBehaviour
createMemoryDriver({ clock? })dev, testsretains messages; outbox() / lastTo() feed the /_x mail panel, ordered by SentMail.at — pass a frozenClock() to choose it
createLogDriver()workers without credentialsone structured line per message through core's logger; bodies never logged
createUnconfiguredDriver(env)a deploy that configured no transportrefuses every send with X_MAIL_CREDENTIAL_MISSING; delivers nothing and claims nothing
createSmtpDriver({ url, from })prodreal ESMTP over Bun.connect: STARTTLS, AUTH PLAIN/LOGIN, quoted-printable MIME
createResendDriver({ apiKey, from })prodone POST /emails, Idempotency-Key on every request

Which one a boot installs

selectMailDriver(env) is the one answer, and x dev and runRole both call it. Nothing about the app changes between environments; the credential does.

envdriver
(nothing set), development / testcreateMemoryDriver() — caught, never sent
(nothing set), staging / productioncreateUnconfiguredDriver(...) — every send is X_MAIL_CREDENTIAL_MISSING
SMTP_URL + MAIL_FROMcreateSmtpDriver(...), MAIL_POOL_SIZE optional
RESEND_API_KEY + MAIL_FROMcreateResendDriver(...)

No credential outside development is a refusal, not the embedded default. The memory driver there answered accepted for mail that never left the process — password resets, receipts and invitations all reported as sent, none delivered, no error anywhere. The refusal lands on the send, not on the boot, so an app that sends no mail still deploys. Which environment this is comes from core's isLocal() (ULTIMATE_ENV, else NODE_ENV, else development) — mail does not own a second reading of it.

Both credentials at once is X_CONFIG_INVALID rather than a winner picked for you, and a transport with no MAIL_FROM is refused at boot instead of on the first send. A host that is not x dev calls selectMailDriver itself, or constructs a driver directly — setMailDriver is the only seam either way.

SMTP

smtps://user:pass@host:465 is implicit TLS; smtp://host:587 starts in the clear and upgrades with STARTTLS. Credentials are percent-decoded, so a password with @ or / works.

RuleWhy
A server offering no STARTTLS is refusedthe message and the password would cross in the clear. allowInsecure: true is the explicit opt-out
Capabilities are re-read after STARTTLSmost servers only advertise AUTH once encrypted, and a cleartext EHLO can be stripped in flight
Any rejected recipient fails the senddelivering to three of four addresses and reporting success is the one outcome a caller cannot detect
poolSize (default 4) caps concurrent connectionsa burst of sends queues instead of opening one socket each
Bcc never reaches a headerit travels in RCPT TO only
Every envelope address is gated for CR/LFMAIL FROM/RCPT TO are built by interpolation, and bcc is the one address no header check ever sees. Refused (X_MAIL_ADDRESS_INVALID), never stripped — a rewritten address delivers somewhere else
The reported id is the Message-IDan SMTP 250 carries nothing a caller could correlate
The Message-ID is stable per messageSMTP has no idempotency protocol, so it is the one identifier a receiving mailbox can collapse a retry on. A fresh token per attempt made a timeout past DATA a second email. It is a one-way digest of mailIdempotencyKey, never the key: the key holds the recipient list, and this header is visible to all of them

Resend

RESEND_API_KEY and a verified sending domain. Every request carries Idempotency-Key: mailIdempotencyKey(message) — a job retry after a timeout hands Resend the identical message, and that header is what makes it one email. 408/409/425/429 and 5xx are retryable (isRetryableStatus, @ultimat3/core); every other non-2xx is a configuration problem that retrying cannot fix.

Framework mails

Registered by importing them — the import IS the registration. FRAMEWORK_MAILS is the list, and registeredMails() / registeredMailIds() answer for an app's own mails as well. There is no x mail command; a host that wants to list or preview a mail calls those and renderMessage().

idInput
welcome{ name, appName, url }
verify-email{ name, url, expiresMinutes }
reset-password{ name, url, expiresMinutes }
invite{ inviterName, orgName, url, expiresHours }
mfa-enrolled{ name, method, at }
security-alert{ name, event, ip, at }

Translating them = shipping mail.* keys in an app catalog. Never edit a template.

Errors

CodeFix
X_MAIL_LOCALE_MISSINGsend(mail, data, { to, locale: ctx.locale })
X_MAIL_TEMPLATE_UNKNOWNexport a defineMail({ id }) and import it (also raised for an unregistered layout)
X_MAIL_DUPLICATErename one of two defineMail({ id }) declarations
X_MAIL_TEXT_MISSINGadd a text-bearing block to the template
X_MAIL_DRIVER_UNAVAILABLEsetMailDriver(createMemoryDriver()) at boot — a wiring bug
X_MAIL_CREDENTIAL_MISSINGset SMTP_URL (or RESEND_API_KEY) and MAIL_FROM in the deployment — an operations one
X_MAIL_HEADER_INVALIDstrip CR/LF from the interpolated value before it reaches a header
X_MAIL_ADDRESS_INVALIDpass a bare addr-spec — an envelope address may hold no control character and no </>
X_MAIL_SEND_FAILEDthe cause names the stage, the provider's status and whether a retry can help — and so does error.retry, which is what sendMailJob acts on: terminal dead-letters a 550 or a rejected credential at attempt 1 instead of sending it four more times

Commands

bun test packages/mail
bun run --filter @ultimat3/mail typecheck

FAQs

Package last updated on 27 Aug 2026

Related posts