Transactional email is sent by packages/mail (@repo/mail). Templates are React Email components rendered to HTML and plain text, then handed to one provider.

Provider

packages/mail/src/provider/index.ts is a single line:
So ZeptoMail is the active provider. The folder also contains adapters for Resend, Postmark, Mailgun, Plunk, Nodemailer (SMTP) and a console logger. They are not selected by configuration. Switching provider means changing that export and redeploying.

ZeptoMail

packages/mail/src/provider/zeptomail.ts posts JSON to the ZeptoMail send API. parseAddress accepts both a bare address and the Name <address> form, so MAIL_FROM can carry a display name.

Environment variables

These are read straight from process.env inside the package. They are not part of the validated env schema in apps/core-api/src/config.ts, so a missing ZEPTOMAIL_TOKEN does not stop the API from booting. It shows up only as failed sends.

sendEmail

packages/mail/src/lib/send.ts. Two call shapes:

It never throws

sendEmail returns false on a provider failure and logs it. It does not throw.
A caller that awaits sendEmail and ignores the return value will report success when no email was sent. If delivery matters to the flow, check the boolean. sendTeamInvitation does this: it logs “mail provider rejected the email” and returns false so the caller can tell the coach.
Template rendering happens before the try, so a rendering error does throw.

Templates

Defined in packages/mail/src/emails/index.ts as mailTemplates. Each is a React component under packages/mail/src/emails/.

Rendering

getTemplate in packages/mail/src/lib/templates.ts:
  1. Loads the mail translations for the locale with getMessagesForLocale.
  2. Calls the component with the context, the locale and the translations.
  3. Takes the subject from the translations under the template id. For notification, a non-empty context.title replaces it.
  4. Renders HTML and a plain-text version with @react-email/render.
Shared pieces are in packages/mail/src/components (Wrapper, Logo, PrimaryButton). lib/direction.ts handles right-to-left layout for Hebrew, including which way the button arrow points.

Who sends email

Login by email OTP is a fallback path. The primary login OTP goes over WhatsApp. See SmartSend WhatsApp.

Adding a template

1

Create the component

Add a file under packages/mail/src/emails/. Its props are your context plus locale and translations.
2

Register it

Add it to mailTemplates in packages/mail/src/emails/index.ts. The key becomes the templateId, and TemplateId picks it up automatically.
3

Add the subject and strings

Add an entry under the same key in the mail translations of every locale in packages/i18n. getTemplate reads subject from there. A missing subject sends an email with an empty subject line.
4

Rebuild the package

Consumers import the built output of @repo/mail.