| name | email-system |
| description | Complete documentation of the email subsystem including template resolution, variable pipeline, rendering engines, SMTP config, and default templates. |
| license | MIT |
Architecture
The email system is in internal/email/ and follows a multi-layered pipeline:
Service (orchestrator)
|-- VariableResolver -> resolves template variables from 4 sources
|-- Renderer -> renders templates with 3 supported engines
|-- Sender -> sends via SMTP (or logs in dev mode)
|-- Repository -> DB access for types, templates, server configs
Entry point: email.Service.SendEmailWithContext(appID, emailTypeCode, toEmail, userID, vars)
Send Pipeline
1. VariableResolver.ResolveVariables() -- build final variable map
2. resolveTemplate() -- find the right template
3. Renderer.RenderTemplate() -- render subject, HTML, text
4. resolveSMTPConfigForTemplate() -- find the right SMTP config
5. Sender.Send() -- deliver via SMTP
Template Resolution Chain
Defined in internal/email/service.go method resolveTemplate:
1. DB: app-specific template
WHERE app_id = ? AND email_type_id = ? AND is_active = true
2. DB: global default template
WHERE app_id IS NULL AND email_type_id = ? AND is_active = true
3. Hardcoded fallback
defaults.go -> GetDefaultTemplate(typeCode)
SMTP Resolution Chain
Defined in internal/email/service.go method resolveSMTPConfig:
1. DB: per-app config
WHERE app_id = ? AND is_active = true AND is_default = true
2. DB: global config
WHERE app_id IS NULL AND is_active = true AND is_default = true
3. Empty config -> dev/fallback mode (log to stdout)
With template override (resolveSMTPConfigForTemplate):
1. If template has ServerConfigID -> use that specific config by ID
2. Otherwise -> standard chain above
3. Apply template-level FromEmail/FromName overrides on top
Variable Resolution Pipeline
Defined in internal/email/resolver.go. Resolution priority (lowest to highest):
| Layer | Source | Method | Example Variables |
|---|
| 1 (lowest) | Static defaults | applyStaticDefaults | DefaultValue from email_types.variables JSONB |
| 2 | App/system settings | applySettingsVars | app_name, frontend_url |
| 3 | User profile | applyUserVars | user_email, user_name, first_name, last_name, locale, profile_picture |
| Always | Fallback | -- | user_email = toEmail if not set |
| 4 (highest) | Explicit caller vars | direct map copy | verification_link, code, reset_link, etc. |
Variable Sources
| Source | Variables | How Resolved |
|---|
user (auto) | user_email, user_name, first_name, last_name, locale, profile_picture | From DB user record |
setting (auto) | app_name, frontend_url | app name from DB/env, frontend_url from env |
explicit (caller) | verification_link, verification_token, reset_link, code, expiration_minutes, change_time, magic_link | Must be passed by calling code |
All variable constants are in internal/email/types.go.
Template Engines
Defined in internal/email/renderer.go:
| Engine | Constant | Syntax | HTML Escaping |
|---|
| Go Template | go_template | {{.AppName}} or {{.app_name}} | Yes (automatic) |
| Placeholder | placeholder | {app_name} | No |
| Raw HTML | raw_html | {{.AppName}} | No (unsafe) |
The renderer converts all variables to both snake_case and PascalCase forms, so templates can use either style.
Subject lines always use Go template syntax regardless of engine.
Email Types (7 built-in)
Constants in internal/email/types.go:
| Code | Constant | Variables Used |
|---|
email_verification | TypeEmailVerification | app_name, verification_link, user_email |
password_reset | TypePasswordReset | app_name, reset_link, expiration_minutes |
two_fa_code | TypeTwoFACode | app_name, code, expiration_minutes |
welcome | TypeWelcome | app_name, user_email |
account_deactivated | TypeAccountDeactivated | app_name, user_email |
password_changed | TypePasswordChanged | app_name, change_time, user_email |
magic_link | TypeMagicLink | app_name, magic_link, expiration_minutes |
Default Templates
Defined in internal/email/defaults.go. All use go_template engine, provide both HTML and plain text bodies, and use responsive table-based layout with consistent styling (indigo #4f46e5 primary color, 600px max width).
Service Methods
Typed Send Methods (in internal/email/service.go)
| Method | Email Type | Extra Vars |
|---|
SendVerificationEmail | email_verification | verification_link (built from FRONTEND_URL + token) |
SendPasswordResetEmail | password_reset | reset_link, expiration_minutes=60 |
Send2FACodeEmail | two_fa_code | code, expiration_minutes=5 |
SendWelcomeEmail | welcome | (none) |
SendAccountDeactivatedEmail | account_deactivated | (none) |
SendPasswordChangedEmail | password_changed | change_time |
SendMagicLinkEmail | magic_link | magic_link, expiration_minutes=10 |
SendAdmin2FACodeEmail | N/A (hardcoded HTML) | Bypasses app-scoped resolution |
SendAdminMagicLinkEmail | N/A (hardcoded HTML) | Bypasses app-scoped resolution |
Management Methods
Template CRUD: GetTemplatesByApp, GetGlobalDefaultTemplates, GetTemplateByID, SaveAppTemplate, SaveGlobalTemplate, DeleteTemplate, ResetTemplateToDefault, PreviewTemplate
Email type CRUD: GetAllEmailTypes, GetEmailTypeByCode, CreateEmailType, UpdateEmailType, DeleteEmailType
Server config CRUD: GetServerConfig, SaveServerConfig, DeleteServerConfig, GetAllServerConfigs, etc.
SMTP Sending (internal/email/sender.go)
Uses gopkg.in/mail.v2.
Dev mode: If SMTP host is empty or "smtp.example.com", logs email to stdout (no error).
TLS handling:
- Port 465: implicit SSL (
d.SSL = true)
- Other ports:
MandatoryStartTLS, MinVersion TLS 1.2
Error behavior:
Send(): on SMTP failure, logs email as fallback and returns nil (email failure doesn't break business flow)
SendTest(): always returns errors (for admin testing)
Database Models
EmailType (pkg/models/email_type.go) -- types with variable definitions as JSONB
EmailTemplate (pkg/models/email_template.go) -- templates scoped to app or global
EmailServerConfig (pkg/models/email_server_config.go) -- SMTP configs scoped to app or global
When To Use This Skill
Load this skill when working on email sending, email templates, SMTP configuration, template variables, or any email-related feature.