Skip to main content

Email (Resend)

The email module sends transactional emails via Resend. Emails are dispatched through a BullMQ queue for reliable async delivery, with a synchronous fallback when Redis is unavailable.

Architecture

EmailService.sendWelcome(...)
|
v
dispatch()
|-- BullMQ available? --> Queue job --> EmailProcessor picks it up --> Resend API
|-- BullMQ unavailable? --> sendDirect() --> Resend API
|-- Resend not configured? --> Log to console

Email Templates

MethodTemplateDescription
sendWelcome(to, name)WelcomeSent after registration
sendMagicLink(to, token)Magic LinkPasswordless login link
sendTeamInvite(to, ...)Team InviteInvitation to join a team
sendPasswordReset(to, token)Password ResetPassword reset link
sendInvoicePaymentFailed(to, name)Invoice FailedNotification of failed payment

All templates are rendered by the renderEmail function which returns a { subject, html } object.

Queue Integration

When BullMQ is available (Redis connected), emails are added to the email queue with these defaults:

  • Retries: 3 attempts with exponential backoff (1s base delay)
  • Cleanup: completed jobs are removed automatically; up to 100 failed jobs are retained for inspection

The EmailProcessor class extends WorkerHost and processes jobs from the email queue. It renders the template and sends via Resend.

Graceful Fallbacks

The module degrades gracefully in two scenarios:

  1. No Redis / BullMQ unavailable: EmailService detects that the queue was not injected (using @Optional()) and sends the email directly in-process via sendDirect().
  2. No RESEND_API_KEY: both the service and processor log the email subject and HTML to the console instead of calling the Resend API. This allows local development without external credentials.

Environment Variables

RESEND_API_KEY=re_...        # Optional in development
EMAIL_FROM=hello@example.com
APP_URL=http://localhost:5173
REDIS_URL=redis://localhost:6379 # Required for queue-based delivery