@growth-labs/email
v0.5.0
Published
Thin transactional email transport for Cloudflare Workers.
Downloads
628
Readme
@growth-labs/email
Thin transactional email transport for Cloudflare Workers.
Use this package when you need to send a one-off email from a Worker right now, without subscriber state, queueing, tracking, or newsletter flows.
If you need campaigns, digests, subscriber management, unsubscribe/preferences, or queue-based delivery, use @growth-labs/mailer instead.
When to use this vs @growth-labs/mailer
Use @growth-labs/email for transactional, one-off sends:
- Password-reset emails
- Purchase receipts and order confirmations
- Magic-link authentication emails
- "Your download is ready" notifications
Use @growth-labs/mailer for newsletter and campaign workflows:
- Newsletter subscription flows (double opt-in, confirmation)
- Campaign sends to subscriber lists
- Open/click tracking
- Unsubscribe handling
- Subscriber preference management
The two packages intentionally do not share infrastructure. email
writes immediately via env.SEND_EMAIL. mailer uses Cloudflare
Queues for batched campaign delivery, D1 for subscriber state, and
injects routes for subscribe/confirm/unsubscribe/preferences/webhooks/tracking.
If a consumer has declared both packages and is unsure, the rule of
thumb is: if the email goes to exactly one person as a direct response
to an action they just took, use email. Otherwise, use mailer.
API
Structured Cloudflare Email Sending:
import { sendEmail } from '@growth-labs/email'
const result = await sendEmail(env, {
from: { email: '[email protected]', name: 'Example' },
to: '[email protected]',
cc: '[email protected]',
subject: 'Your login code',
text: 'Your code is 123456',
html: '<p>Your code is <strong>123456</strong></p>',
headers: {
'X-Workflow': 'login-code',
},
})Provider-neutral raw MIME:
import { send } from '@growth-labs/email'
const result = await send({
provider: 'fastmail',
credentials: {
provider: 'fastmail',
jmapToken: env.FASTMAIL_JMAP_TOKEN,
},
rfc822Bytes: canonicalMimeBytes,
envelopeFrom: '[email protected]',
envelopeTo: ['[email protected]'],
})Providers
Structured sends use Cloudflare Email Service via env.SEND_EMAIL. If the
binding is missing, sendEmail() throws.
Raw MIME sends through sendEmail() use Cloudflare's official REST endpoint:
await sendEmail(env, {
from: { email: '[email protected]', name: 'Support' },
to: '[email protected]',
rawMime: canonicalMimeBytes,
rawMessageId: '<[email protected]>',
})For raw MIME delivery through sendEmail(), env must include
CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN.
Provider-neutral raw MIME sends use send():
provider: 'cloudflare'sends through Cloudflare's raw MIME REST endpoint using{ provider: 'cloudflare', accountId, apiToken }.provider: 'fastmail'sends through Fastmail JMAP using{ provider: 'fastmail', jmapToken }. Optional overrides are available forjmapSessionUrl,accountId,identityId,sentMailboxId, anddraftsMailboxId.provider: 'google'credentials are typed for future Workspace support, but the transport currently returnsgoogle_not_implemented.
The MIME message should include the display headers (From, To, Subject,
Message-ID, Content-Type, and threading headers as needed); envelopeFrom
and envelopeTo control the SMTP envelope.
Scope
- Immediate transactional delivery
- Single or multiple recipients
- Plain text required and HTML optional for structured sends
- CC, BCC, reply-to, custom headers, and attachments
- Raw MIME delivery for canonical RFC 5322 messages
- Provider-neutral raw MIME delivery through Cloudflare or Fastmail JMAP
{ success, provider, messageId?, error?, errorCode? }result{ providerAcceptedAt, providerMessageId, errorCode, errorDetail }result forsend()
Non-Goals
- No Astro integration
- No injected routes or middleware
- No D1 state
- No queue fan-out
- No campaigns or digests
- No unsubscribe or preferences flows
- No tracking pixels or click rewriting
