Skip to main content
Localizia

Email

Overview

Email is provider-agnostic behind src/lib/email/. It supports Resend (the default) and SMTP via Nodemailer, chosen in src/config/email.config.ts. Templates are React Email components in src/emails/.

EMAIL_PROVIDER=resend         # "resend" | "nodemailer"
EMAIL_FROM=[email protected]
EMAIL_FROM_NAME="Your App"
EMAIL_REPLY_TO=[email protected]
RESEND_API_KEY=re_...         # when provider=resend
# or SMTP_HOST / SMTP_PORT / SMTP_SECURE / SMTP_USER / SMTP_PASS

Sending mail

Prefer the named senders in src/lib/email/index.ts over calling the low-level sendEmail() directly — they render the right template with typed props:

await sendPasswordResetEmail({ to, url, locale })
await sendOrgInvitationEmail({ to, orgName, inviterEmail, url, locale })
await sendMagicLinkEmail({ to, url, locale })

The kit ships senders for password reset, email verification, magic link, org invitation (and revocation), email-change verification, contact form, and the billing lifecycle (payment failed, subscription canceled, dispute created, trial ending).

The generic sender remains available for one-off mail:

await sendEmail({ to, subject, html, text })

Templates are localized

Every template accepts a locale prop and renders translated strings, so an invitation reads in the recipient's language. Templates share a common EmailLayout. To preview them while editing:

pnpm email:dev

Adding a template

  1. Create a React Email component in src/emails/ that takes typed props (including locale).
  2. Add a named sender in src/lib/email/index.ts that renders it and calls sendEmail().
  3. Call your sender from the relevant tRPC procedure or route.

Failures don't crash callers

sendEmail() catches and logs provider errors (redacting the recipient) rather than throwing, so a transient email outage doesn't take down the request that triggered it. Delivery problems surface in your logs and Sentry, not as user errors.