Email
Email Overview
Transactional email — send from @repo/email, preview in apps/email
Two packages:
| Package | Role | How to run |
|---|---|---|
@repo/email (packages/email) | Templates + send via Resend or Nodemailer | imported by @repo/auth |
apps/email | React Email preview of packages/email/templates/ | pnpm --filter email dev → http://localhost:3003 |
Caught SMTP in local dev is MailDev (http://localhost:1080), not the preview app. Preview never sends.
What actually sends
Better Auth hooks in packages/auth/server.ts call the wired senders (password reset, verification, magic link, OTPs, org invitation). Stripe lifecycle mail is packages/auth/stripe-hooks.ts.
// packages/auth/server.ts — a live send
sendResetPassword: async ({ user, url }) => {
await sendPasswordResetEmail({
email: user.email,
resetUrl: url,
});
},sendWelcomeEmail exists on @repo/email and is not called from the app. i18n catalogs under packages/email/i18n/ are unused — template copy is hardcoded English.
Providers
Only resend and nodemailer. Unset EMAIL_PROVIDER auto-detects: production + RESEND_API_KEY → Resend, otherwise Nodemailer (localhost:1025). See Setup and Providers.
Next Steps
- Setup Guide — env vars in
apps/web/.env.local - Providers — Resend vs Nodemailer
- Templates — wired vs preview-only
- Local Development — MailDev