Open-Source Wikis

/

Bitwarden Server

/

Systems

/

Mail

bitwarden/server

Mail

Active contributors: every team — UI Foundation owns the framework.

Purpose

Bitwarden's transactional email is rendered server-side from MJML templates compiled to Handlebars and dispatched via one of several SMTP-style backends. Every team that ships a feature also ships its own email templates (welcome, invite, share notice, password change, two-factor recovery, etc.) under a team-owned subfolder. The single render-and-send entry point is IMailService / HandlebarsMailService.

Directory layout

src/Core/Platform/Mail/
├── HandlebarsMailService.cs           # 1,648 lines — one method per template
├── HandlebarsMailEngine.cs            # Wraps Handlebars.Net for rendering
├── IMailEnqueuingService.cs           # Optional queue front-end
├── BlockingMailEnqueuingService.cs    # Default: send immediately
├── ServiceCollectionExtensions.cs

src/Core/MailTemplates/
├── Mjml/                              # MJML templates per team (UI Foundation owns root config)
│   ├── .mjmlconfig
│   ├── AdminConsole/
│   ├── Auth/
│   ├── Billing/
│   ├── Tools/
│   ├── Vault/
│   └── ...
└── (compiled Handlebars output committed alongside)

The IMailService interface is in src/Core/Services/IMailService.cs (yes, it lives in Services rather than Platform/Mail for historical reasons). It exposes ~80 methods, one per template.

Key abstractions

Type Path Description
IMailService src/Core/Services/IMailService.cs Interface with one method per template.
HandlebarsMailService src/Core/Platform/Mail/HandlebarsMailService.cs The implementation — for each method, builds a model, picks the template, and calls HandlebarsMailEngine.RenderAsync.
IMailDeliveryService src/Core/Services/IMailDeliveryService.cs (with implementations under src/Core/Services/Implementations/) Pluggable delivery: MailKitSmtpMailDeliveryService, SendGridMailDeliveryService, MailgunMailDeliveryService, AmazonSesMailDeliveryService, NoopMailDeliveryService.
IMailEnqueuingService src/Core/Platform/Mail/IMailEnqueuingService.cs Indirection for queueing mail in cloud (background dispatch).

How it works

graph LR
    Code["domain code\n(IMailService.SendXxxAsync)"] --> Service["HandlebarsMailService"]
    Service --> Engine["HandlebarsMailEngine\n(MJML→HTML)"]
    Engine --> Templates["MJML/<Team>/*.html.hbs\n*.text.hbs"]
    Service --> Enqueue["IMailEnqueuingService"]
    Enqueue --> Delivery["IMailDeliveryService"]
    Delivery --> Backend["SMTP / SendGrid / Mailgun / SES / NoOp"]

For each template there is an HTML version and a plaintext version (both Handlebars files). MJML is compiled at build time so the runtime only sees the resolved HTML.

Adding a new email

  1. Add MJML templates under src/Core/MailTemplates/Mjml/<Team>/<template>.mjml.hbs and the compiled .html.hbs and .text.hbs siblings (the build pipeline regenerates from the MJML).
  2. Add a model class for the template under src/Core/MailTemplates/<Team>/Models/ (or the team's Models folder).
  3. Add a method to IMailService and implement it in HandlebarsMailService (build the model, render, enqueue).
  4. Call the new method from the appropriate command / service.

@bitwarden/team-ui-foundation owns the framework (Mjml/.mjmlconfig and shared partials). Each team owns its subdirectory: per CODEOWNERS, src/Core/MailTemplates/Mjml @bitwarden/team-ui-foundation # Teams are expected to own sub-directories of this project.

Backends and configuration

globalSettings.Mail.Smtp configures an SMTP delivery (used in cloud and self-host). Optional alternates:

  • globalSettings.Mail.SendGridApiKey → SendGrid.
  • globalSettings.Mail.MailgunApiKey → Mailgun.
  • globalSettings.Mail.AmazonSesAccessKeyId → SES.

The NoopMailDeliveryService is used in tests and dev when no backend is configured.

MailCatcher runs in dev/docker-compose.yml (profile mail) on port 1080 so local mail lands somewhere visible.

Entry points for modification

  • Add a transactional email → see "Adding a new email" above.
  • Change the from-address / display name → globalSettings.Mail.ReplyToEmail and friends.
  • Add a new delivery backend → implement IMailDeliveryService and register it conditionally in services.AddDefaultServices.
  • Switch to async dispatch → swap BlockingMailEnqueuingService for a queue-backed IMailEnqueuingService implementation (cloud uses the queue path).

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Mail – Bitwarden Server wiki | Factory