Mails
packages/mail owns transactional email: Maizzle-based templates plus a small provider layer. The dashboard uses it for organization invitations and other auth-driven messages.
Providers
MAIL_PROVIDER selects the transport. When it is absent, a configured RESEND_API_KEY selects Resend so hosted integrations such as Vercel's work without a second selector variable. With neither set, the default is console, which logs instead of delivering — an unconfigured deployment cannot silently attempt real delivery. An explicit MAIL_PROVIDER always wins.
| Provider | Configuration |
|---|---|
console | None. Logs the rendered message. The credential-free default; not for production. |
resend | RESEND_API_KEY |
smtp | SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_SECURE |
MAIL_FROM sets the sender address for every provider.
SMTP_SECURE selects implicit TLS: true on port 465, false on port 587 (STARTTLS).
Templates
Templates are authored with Maizzle and declared in the package's template registry:
mailTemplatesmaps aMailTemplateIdto its template file, subject, and context fields;MailTemplateContexttypes the data each template needs;maizzle.config.tsis discovered by Maizzle's ownrender()call by filesystem convention.
Adding a template means adding it to the registry with a typed context, keeping delivery and content concerns separate: providers never know what a template says, and templates never know how mail is sent.
Compiling templates
Rendering a template runs Maizzle's pipeline — Vue SSR, CSS inlining, plaintext — on top of a Vite SSR server, which needs a bundler with its platform-native binary and a writable working directory. The deployments that send mail are serverless functions with neither, so rendering happens once at build time instead:
aube run mail:compile
That writes packages/mail/src/util/compiled-templates.json, which is checked in and bundled into the package. Each template is rendered once per combination of the conditional fields the registry declares — the halves a template renders away when empty — with every other field standing in as a placeholder. sendEmail then picks the variant its context matches and substitutes the values, escaping them for the HTML part and leaving them bare in the plaintext one.
Run it after editing anything under packages/mail/emails/. aube test fails when the checked-in artifact no longer matches what the templates render.
Previewing templates
apps/mail-preview wraps Maizzle's dev server around the package's templates:
aube run mail:preview
This serves every template at http://localhost:3005 with hot reload, rendered against the sample values in apps/mail-preview/maizzle.config.ts. The app owns no templates of its own — its emails/ directory is a symlink into packages/mail/emails/, so edits there reload live. Its build script compiles every template to static HTML in apps/mail-preview/dist/, which CI runs as a template-compilation check.
resend or smtp).Frontend
apps/dashboard is a Nuxt 4 app and the single deployable artifact: it serves the UI, the control plane, and authentication from one origin.
Internationalization
The dashboard ships English and Italian. Locale policy lives in packages/i18n; the dashboard wires it into @nuxtjs/i18n with strategy: 'no_prefix' (the locale is a user preference, not a URL segment).