Notifications (Email + SMS)
This document defines generic engineering standards for sending emails and SMS notifications in backend services.
Scope:
- Transactional email (support + system notifications)
- Transactional SMS
- Event-specific “notification processes” (schemas + APIs + orchestration)
- Security controls for externally-triggered notification endpoints
- Testing patterns for notification flows
Module layout (standard pattern)
notifications/
├── config.py # from-address configuration via BaseSettings (or equivalent)
├── email_sender.py # async email sending + templates + rate limiting + attachments
├── sms_sender.py # async SMS sending (provider HTTP API) + optional templates
├── templates/ # message templates (HTML and/or plain text)
└── processes/ # event-specific processes + APIs + schemas
├── <process_name>/
│ ├── apis.py
│ ├── process.py
│ └── schemas.py
└── ...
Email standards
Template rendering
- Store templates under
notifications/templates/. - Render templates via Jinja2 using
autoescape=Truefor HTML email bodies. - Prefer template context over string concatenation so templates stay consistent and testable.
Sender identity (“from” address)
- Support at least two categories:
- support: user-facing support communications
- notification: system/operations notifications
- Select the from-address based on the category, with environment-based overrides.
Delivery function contract
Implement a single async entry point (e.g. send_email(...)) that supports:
to_email(single recipient)subjecttemplate_name+template_contextis_html(HTML vs text)type(support vs notification)attachments(optional file paths or file-like payloads)bcc_emails(optional list)
Attachments + BCC
- Attachments must be encoded into the provider payload (commonly base64 for JSON APIs).
- BCC should be supported for auditing/ops workflows.
Provider rate limiting + batch sends
- Apply a provider-safe send rate limiter inside the sender module (e.g. lock + timestamps).
- Provide a
send_batch_emails(...)helper for multi-recipient notifications. - If per-recipient personalization is required, send individualized emails with rate limiting (don’t break provider limits).
SMS standards
Delivery + templates
- Send SMS via an async HTTP client (e.g.
httpx.AsyncClient) to your SMS provider API. - Support either:
- direct
message, or template_name+template_contextrendered into an SMS body.
- direct
- If you use template rendering for SMS, disable HTML autoescaping for text bodies (
autoescape=False) and keep templates text-safe.
Authentication + configuration
- Read provider credentials and sender identity from environment/config.
- Validate credentials on send and fail with a clear error when missing.
Notification “processes” (event-specific flows)
Use notifications/processes/<process_name>/ to keep notification flows predictable:
schemas.py- Validate request bodies with strict constraints (max lengths, required fields).
- Sanitize only fields that intentionally accept HTML (rich text). For normal text fields, use validation + max lengths (do not mutate inputs).
apis.py- Define the FastAPI router.
- Apply rate limiting per endpoint.
- Enforce authentication/authorization appropriate to the endpoint (JWT token, API key, etc.).
- Keep orchestration minimal: validate → call process → return response.
process.py- Resolve recipients (often from persisted configuration).
- Build subject + template context.
- Call the sender (
send_email,send_batch_emails,send_sms). - Return a structured result: successes, failures, counts, and actionable messages.
Security standards for externally-triggered notification endpoints
For endpoints hit by external clients (widgets, integrations, CI/reporting):
- Fail closed when auth configuration is missing.
- Use Bearer token patterns (JWT or API key) and validate ownership/scope (e.g., token subject matches the target resource id).
- Add an
OPTIONShandler when the endpoint is called cross-origin. - Apply strict rate limiting (document the limit per endpoint).
Error notifications (exception handlers)
Support an optional “email admin” / “notify ops” behavior:
- Exception handlers may trigger an admin notification email when an error is marked as “notify”.
- Capture minimal, high-signal debug context (request path/method, selected headers, request body where safe).
- Ensure the notification path is best-effort: log failures but don’t crash the main request path.
Testing standards for notifications
- Endpoint tests:
- Use an ASGI-bound
httpx.AsyncClient(ASGITransport(app=app)) so tests do not use real network. - Patch the process function called by the endpoint and assert it is invoked for valid requests.
- Cover auth failure cases (missing header, invalid token).
- Use an ASGI-bound
- Process tests:
- Unit-test template context mapping by patching
send_email/send_smswithAsyncMock. - Assert the process passes the expected
template_name,subject, and critical context fields.
- Unit-test template context mapping by patching