Skip to content

Email System

Overview

packages/email (@mtl-rent/email) provides transactional email templates, rendering, and delivery via a RabbitMQ queue. Emails are rendered with React Email and sent through Resend.

Architecture

API route (POST /inquiries, POST /saved-searches)
  → fastify.publishEmail({ action, to, locale, data })
  → RabbitMQ exchange "email" → queue "email.send"
  → Scraper worker email consumer
  → @mtl-rent/email sendEmail() → renderEmail() → Resend API

Scraper periodic task (saved-search-alerts)
  → publishEmailMessage() → same RabbitMQ queue → same consumer
  • Publisher: API uses @mtl-rent/email/publish (Fastify plugin at services/api/src/plugins/email.ts)
  • Consumer: Scraper worker (services/scraper/src/consumers/email-consumer.ts) — prefetch: 3, max retries: 3
  • Dead letters: Failed emails go to the DLQ after 3 retries

Templates

ActionCategoryTriggerDescription
welcomeTransactionalPOST /auth/registerEmail verification on signup
email-changeTransactionalChange emailVerify new email address
reset-passwordTransactionalForgot passwordTime-limited reset link
password-changedTransactionalAfter password changeSecurity notification
account-deactivatedTransactionalAccount deletion30-day recovery window
subscription-alertsAlertsPOST /saved-searchesAlert subscription confirmation
new-listing-matchAlertsDaily 8am jobNew listings matching user's alert
inquiry-sentMessagingPOST /inquiriesConfirmation to sender
inquiry-receivedMessagingPOST /inquiriesNotification to landlord

All templates support EN and FR locales.

Package Exports

typescript
// Main: types, renderEmail, registry
import { renderEmail, templateRegistry, type EmailMessage } from "@mtl-rent/email";

// Publisher: RabbitMQ email queue publisher
import { initEmailPublisher, publishEmail, closeEmailPublisher } from "@mtl-rent/email/publish";

// Send: Direct Resend sender (used by consumer)
import { sendEmail } from "@mtl-rent/email/send";

Adding a New Template

  1. Define the data type in packages/email/src/templates/_lib/types.ts:

    typescript
    export interface MyNewEmailData { name: string; link: string; }

    Add to EmailAction union and EmailDataMap.

  2. Add translations in packages/email/src/templates/_i18n/en.ts and fr.ts:

    typescript
    'my-new-email': { subject: '...', heading: '...', body: '...', buttonText: '...' }
  3. Create the template in packages/email/src/templates/<category>/my-new-email.tsx:

    tsx
    import { Layout } from '../components/layout';
    export default function MyNewEmail({ locale = 'en', data }) { ... }
  4. Register metadata in packages/email/src/templates/_lib/registry.ts.

  5. Add to render map in packages/email/src/templates/_lib/render.ts.

  6. Build: pnpm --filter @mtl-rent/email build

Preview Server

bash
pnpm email:preview
# → http://localhost:3100

Interactive UI with:

  • Sidebar listing all 9 templates by category
  • EN/FR locale toggle
  • Live HTML preview
  • Send Test button (requires RESEND_API_KEY + TEST_EMAIL_TO in .env)

Testing

bash
# Send a test email via CLI
pnpm email:send -- --action reset-password --locale en --to user@example.com

Configuration

Environment variables (in .env):

RESEND_API_KEY=re_your_api_key_here
RESEND_FROM_EMAIL=MTL Rent <alerts@mtl-rent.fesenko.net>
AMQP_URL=amqp://mtl:mtl@localhost:5672
TEST_EMAIL_TO=dev@example.com    # For preview server Send Test

Key Files

FilePurpose
packages/email/src/index.tsMain exports (types, render, registry)
packages/email/src/publish.tsRabbitMQ publisher
packages/email/src/send.tsResend sender
packages/email/src/templates/_lib/types.tsEmail action types + data interfaces
packages/email/src/templates/_lib/render.tsReact → HTML + plain text
packages/email/src/templates/_lib/registry.tsTemplate metadata
packages/email/src/templates/_i18n/EN/FR translations
packages/email/src/templates/components/layout.tsxShared email layout
packages/email/src/preview/server.tsDev preview server
services/api/src/plugins/email.tsFastify email publisher plugin
services/scraper/src/consumers/email-consumer.tsRabbitMQ email consumer
services/scraper/src/mq/topology.tsEmail exchange/queue declarations