Email HTML Sanitization: Separate Trusted Templates From User Content
Separate trusted email templates from untrusted names, comments and rich text, with context-aware encoding and a reviewed sanitization policy for previews.
TL;DR
- Treat template markup and user-supplied values as distinct inputs: keep trusted template structure under application control and prevent arbitrary user content from becoming active HTML.
- Use a narrow, explicit sanitization and rendering policy: map every input to each rendering context, choose a reviewed sanitizer policy, and limit allowed elements to what the product actually needs.
- Verify safety and usability with concrete tests and delivery ownership: run fixtures against real preview and message paths, and keep delivery responsibility and sanitizer configuration explicitly documented.
Identify every place untrusted content enters
An email template can contain trusted application markup and values supplied by users or external systems. Treat those as different inputs. A customer name, support comment or imported product description should not gain permission to become arbitrary HTML merely because it appears inside a message.
Draw a simple map from input source to rendered output. Include the sent email, browser preview, support dashboard and any archived message view. The same content may cross several rendering boundaries, and a mailbox client's behavior does not establish that your own browser preview is safe.
Prefer text when formatting is unnecessary
For ordinary names, subjects and short labels, preserve the value as text and apply the appropriate output handling for its context. Do not enable rich HTML input merely to support punctuation or non-Latin characters.
Consider an illustrative support notification: the customer's display name belongs in a heading, while the submitted comment belongs in a body section. The name should remain text. If the product supports formatted comments, that is a separate, explicitly bounded capability requiring its own policy.
This distinction makes the system easier to reason about and reduces the number of places where a sanitizer must preserve complex markup.
Match the protection to the output context
The OWASP XSS prevention guidance distinguishes output encoding from HTML sanitization and explains that context matters. A value placed in text, an attribute or a URL cannot be treated as interchangeable.
Use framework protections correctly and avoid bypasses that insert untrusted raw markup. If HTML must be accepted, use a maintained sanitizer with a reviewed policy rather than a handwritten regular expression that removes a few suspicious strings.
The goal is not to make hostile input look harmless in one example. It is to maintain a clear boundary between data and permitted markup across every supported rendering path.
Define a narrow rich-text policy
Write down which elements, attributes and URL schemes the product needs. For a support comment, paragraphs, emphasis and ordinary links may be sufficient. Embedded forms, scripts, arbitrary styling or externally loaded resources may be unnecessary for the task.
Choose the policy based on the product's actual editor and reader experience. A broad “allow everything except scripts” rule can preserve behavior the team never intended to support.
Also decide how unsupported formatting appears after sanitization. The recipient should still receive an understandable comment, and the sender should not be misled into thinking an important element will survive when it will be removed.
Review links as destinations, not only markup
A syntactically permitted link can still lead somewhere inappropriate. Separate structural sanitization from the business policy for destinations, redirects and displayed labels.
For the support example, an external link in a customer comment should not be restyled to look like an official account-action button. Distinguish user-provided content from application instructions so the recipient can understand who supplied it.
Do not place arbitrary user values into an application's trusted action URL. Construct those destinations from validated identifiers and server-controlled routes according to the workflow's authorization rules.
Keep browser previews within the same boundary
A preview page is part of the application, not a disposable development convenience. Rendering stored email content there can expose staff or customers to input that originated elsewhere.
Use a deliberately isolated preview design and the appropriate sanitization and browser protections for that implementation. Do not assume that because many email clients limit active content, a normal web page will impose the same restrictions.
Review quoted replies and imported historical messages too. New templates may be carefully controlled while an archive view later inserts older raw content through a different code path.
Related reading: Scheduled Email Delivery: Store the Time, Content and Cancellation State.
Build tests around the allowed experience
Create fixtures for normal text, supported emphasis, a long link, malformed markup and content that should be rejected or stripped. Verify both safety properties and useful output: the comment remains readable, official actions remain distinct and unsupported elements do not create unintended behavior.
Include tests after any sanitizer or rendering-library update, especially when a new editor feature expands the permitted markup. Keep the dependency maintained and avoid mutating sanitized HTML afterward in a way that reintroduces untrusted markup.
These fixtures should run against the actual preview and message preparation paths. Testing a helper in isolation is insufficient if another component later bypasses it.
Keep delivery responsibility explicit
The application should prepare safe, intentional content before passing it to SendDart. A delivery API accepting an HTML string is not evidence that arbitrary user content has been reviewed or sanitized for every place your product displays it.
Document the input policy, sanitizer configuration and ownership of preview rendering. The useful result is a system where trusted template structure stays under application control, user content remains within its allowed scope and recipients can distinguish official instructions from quoted or submitted material.
Related reading: Best Email API: Choose With a Production Acceptance Test.