Send transactional email through SMTP and test it locally with Mailpit.
Transactional email is sent with Nodemailer through SMTP. The email service lives
in @prolifto/infrastructure.
Local development
Start Mailpit:
pnpm mail:up- SMTP:
localhost:2525 - Web UI and API: http://localhost:8025
Mailpit accepts any username and password and captures every message. Use the
SMTP_* values from the env examples. Reset captured messages with:
pnpm mail:resetWhat email is sent
| Trigger | |
|---|---|
| Sign-in / verification OTP | Admin or storefront auth |
| Organization invitation | A member is invited to a store |
| Order confirmation | An order is created, or an admin resends it |
| Account deletion link | A user requests account deletion |
There is no marketing email or campaign system in the current codebase.
SMTP configuration
SMTP_FROM="Prolifto <dev@example.com>"
SMTP_HOST="localhost"
SMTP_PORT="2525"
SMTP_USER="dev@example.com"
SMTP_PASS="mailpit"Port 465 enables implicit TLS; other ports use STARTTLS when the server
supports it. The sender uses a 5-second connection timeout and a 10-second
socket timeout and throws on delivery failure so Inngest can retry.
In production, use a real SMTP provider and a sender address that the provider
has verified. Never use a Vercel domain as SMTP_FROM. Do not send from
example.com in production.
Adding an email
- Create a template builder in
packages/infrastructure/src/email. - Reuse
email-layout.tsfor consistent branding. - Escape user-supplied values with
escape-html.ts. - Send it from a service or an Inngest function.
- Add a test for the rendered subject and key content.
- Test locally in Mailpit before merging.
Debugging
- No message arrives: check Mailpit is running and the
SMTP_*values match. - Authentication fails in production: verify the SMTP username, password, and port, and confirm the provider allows the sender.
- Messages are retried: the send function throws on failure; check the Inngest run for the error.
- Wrong links: check
NEXT_PUBLIC_APP_URL,NEXT_PUBLIC_FRONTEND_URL, and the storefront domain configuration.