Skip to content

vela-mail ​

Transactional email templates + transport package for VelaPay. Published as @velapay/mail (v0.2.0). Owns every customer-facing email VelaPay sends — auth verification, receipts, plan-change confirmations, cancellation notices, dunning, magic-link login.

Like vela-webhook, this is a package, not a standalone Worker. The actual delivery happens from the dashboard worker (or any other consumer Worker) using either Cloudflare Email Service (production) or Mailpit (local development).

Purpose and Role ​

  • Render every VelaPay transactional email from shared React Email components — one place to update brand, copy, and layout.
  • Provide a transport abstraction so consumers don't care whether they're hitting Cloudflare Email or a local Mailpit instance.
  • Carry brand tokens and typography from @vela/brand into emails so transactional mail looks like the rest of the product.
  • Be the consumer of vela-mail-smoke — every template renders + dispatches in CI before each release.

Tech Stack ​

TechnologyVersionPurpose
TypeScript^5.8.xLanguage
Bun1.3.xRuntime + bundler
React EmaillatestTemplate authoring (JSX → HTML email)
Cloudflare Email (send_email binding)—Production transport
Mailpit—Local-dev SMTP capture for visual review
BiomelatestLint + format

Built into dual CommonJS + ESM via build.ts.

Package Surface ​

ExportPurpose
send(template, props, transport)Render and dispatch a typed template via the configured transport
Template enumAll supported template IDs (e.g. auth.verification, invoice, plan.change, cancellation, dunning, receipt)
MailTransport interfacePluggable transport (Cloudflare Email production / Mailpit local)
Each template moduleAuthored as React Email JSX, exports subject + Component

Directory Structure ​

vela-mail/
├── emails/                    # React Email templates (JSX)
│   ├── auth-verification.tsx
│   ├── invoice.tsx
│   ├── plan-change.tsx
│   ├── cancellation.tsx
│   ├── dunning.tsx
│   ├── receipt.tsx
│   └── ...
├── src/
│   ├── send.ts                # Template + transport dispatch
│   ├── transports/
│   │   ├── cloudflare.ts      # CF send_email binding
│   │   └── mailpit.ts         # Local SMTP for development
│   └── index.ts
├── tests/                     # bun:test rendering + dispatch tests
├── COPY-REVIEW.md             # Track when copy was last reviewed per template
├── build.ts
├── package.json
└── tsconfig.json

Consumers ​

ConsumerWhat it sends
vela-dashboard (worker)Invoice and payment-receipt emails, plan-change confirmations, cancellation notices, dunning, team invites
vela-portal (worker)Magic-link login, subscription state-change confirmations
vela-mail-smokeAll templates — exercises render + dispatch end-to-end before release

Commerce payment truth, refund/credit-note PDFs, and document persistence remain in vela-dashboard; vela-mail owns typed email presentation and transport, not refund or reconciliation state.

Deployment Target ​

  • Workspace package — linked into consumer Workers via file:../vela-mail.
  • Production transport — Cloudflare Email Service send_email binding. Consumer Workers declare allowed sender addresses (e.g. mail@velapay.io, billing@velapay.io) in their wrangler.toml.
  • Local transport — Mailpit catches outbound mail during development. No real mail is sent locally.

Notable Design Decisions ​

React Email ​

Templates author in JSX with the same component model as the rest of the workspace. Rendering happens at send time inside the Worker — no separate template-compile step. Designers preview templates by running the React Email dev server locally.

Transport Abstraction ​

Production uses send_email Worker bindings (Cloudflare Email Service). Local development routes to Mailpit. The same send() API works in both — no environment-specific branching at the call site.

Allowed-Sender Per Template Type ​

Cloudflare Email requires per-binding allowed-sender addresses. The wiring is per-Worker (not per-template) — consumer wrangler.toml files declare bindings (e.g. AUTH_EMAIL mapped to mail@velapay.io, BILLING_EMAIL mapped to billing@velapay.io) and vela-mail selects the right binding based on template type.

Co-Versioned with Brand ​

Email visual language tracks @vela/brand versions. A brand bump that changes typography or color requires a parallel vela-mail release so transactional mail doesn't drift from the product UI.

Internal knowledge base for the Vela Labs workspace.