bitwarden/server
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
- Add MJML templates under
src/Core/MailTemplates/Mjml/<Team>/<template>.mjml.hbsand the compiled.html.hbsand.text.hbssiblings (the build pipeline regenerates from the MJML). - Add a model class for the template under
src/Core/MailTemplates/<Team>/Models/(or the team's Models folder). - Add a method to
IMailServiceand implement it inHandlebarsMailService(build the model, render, enqueue). - 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.ReplyToEmailand friends. - Add a new delivery backend → implement
IMailDeliveryServiceand register it conditionally inservices.AddDefaultServices. - Switch to async dispatch → swap
BlockingMailEnqueuingServicefor a queue-backedIMailEnqueuingServiceimplementation (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.