If you build web apps, CSP is one of those controls that feels clean and satisfying. You define what the browser may load, block everything else, and sleep a little better.
Then email shows up and ruins the neat mental model.
CSP for email templates and HTML emails sounds like a natural extension of web security. Same HTML, same browser engine somewhere in the stack, same risks from injected content. But email clients are a different universe. Some strip tags. Some rewrite links. Some proxy images. Some ignore modern security controls entirely. And many of them treat your carefully crafted HTML like a suspicious package.
So the short version is this:
- CSP is great for the web pages that render or preview email templates
- CSP is mostly not a reliable security control inside delivered HTML emails
- If you’re trying to secure email content, sanitization and email-safe markup matter far more than CSP
That’s the comparison developers actually need.
The two things people mean by “CSP for email”
These get mixed together all the time.
1. CSP on a website that manages or previews email templates
This is a normal web security problem.
You have:
- an admin UI for editing email templates
- a preview page that renders user-generated merge fields
- a transactional email builder
- a WYSIWYG editor with saved HTML snippets
Here CSP absolutely helps. Your app runs in a browser you control, and the browser understands CSP headers or a meta policy.
2. CSP inside the HTML email itself
This is the messy one.
You send HTML email and hope the recipient’s email client will respect a CSP policy. In practice, support is inconsistent to nonexistent. Many clients strip <head>, ignore headers, remove scripts entirely, rewrite markup, and generally don’t behave like a normal browser tab.
If your plan depends on the recipient’s email client enforcing CSP, I would not trust it.
The practical comparison
Here’s the blunt version.
| Use case | CSP value | Reliability | Recommendation |
|---|---|---|---|
| Email template editor in your app | High | High | Use CSP |
| Email preview page on your site | High | High | Use CSP |
| Browser-based webmail rendering you control | Medium to High | Medium | Use CSP if you control rendering |
| Delivered HTML email in third-party clients | Low | Low | Do not rely on CSP |
That split is the whole story.
Where CSP works well: email template platforms and preview UIs
If you render email templates on your own domain, treat them like any other dynamic HTML surface.
Typical risks:
- stored XSS in template content
- untrusted merge fields like
{{first_name}} - pasted HTML from marketers
- unsafe WYSIWYG output
- third-party widgets in preview pages
- asset loading from random CDNs
A decent CSP helps contain mistakes.
Here’s a realistic baseline for an email preview application:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-r4nd0m';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
Why this shape?
default-src 'self'keeps the baseline tightscript-srcwith a nonce allows only scripts you explicitly blessstyle-src 'unsafe-inline'is often unavoidable in email preview tooling because email HTML loves inline stylesimg-src data: https:is practical because email templates often use data URIs and remote imagesframe-ancestors 'none'blocks clickjacking on internal preview/admin pagesobject-src 'none'is just standard hygiene
If you need examples, https://csp-examples.com is useful for adapting common policies.
A real-world CSP header, and what it tells us
Here’s the real CSP from headertest.com:
content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-NmQ1YTRiYjktYTIxMS00NTU1LTkzZTQtYjBhMjViZWVlMzhk' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.headertest.com https://collect.tallytics.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com; frame-src 'self' https://consentcdn.cookiebot.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
This is a normal modern website CSP. It says a lot:
- the site uses a nonce for scripts
- it uses
strict-dynamic, which is a strong pattern for script trust propagation - it allows inline styles because real sites often still need them
- it tightly defines
connect-src,frame-src,form-action, andobject-src
This kind of policy makes sense for a web app, dashboard, or preview UI.
It does not map cleanly to HTML email. Why?
- email clients generally block JavaScript anyway
- many email clients won’t enforce the policy
- third-party analytics and script sources are irrelevant in most email clients
- parts of the HTML document where CSP would live may be stripped or ignored
So if someone asks, “Can I use my site CSP for emails?” the honest answer is: for template previews, yes; for delivered emails, no.
Pros of CSP for email templates
When the template is rendered on your own site, CSP has real benefits.
1. It limits damage from stored XSS
Template systems are magnets for stored XSS. Someone pastes sketchy HTML, a merge field contains hostile content, or a sanitizer misses an edge case. CSP won’t fix the bug, but it can stop inline script execution or third-party script loads.
2. It gives you control over remote assets
Email builders often pull images, fonts, tracking assets, and editor dependencies from all over the place. CSP forces you to be explicit.
That’s healthy. Most teams discover asset sprawl only after writing a policy.
3. It pairs well with nonces in admin UIs
If your email editor needs JavaScript, nonce-based CSP is still the cleanest option.
Example in an Express app:
app.use((req, res, next) => {
const nonce = crypto.randomUUID();
res.locals.nonce = nonce;
res.setHeader(
"Content-Security-Policy",
[
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}'`,
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"object-src 'none'"
].join("; ")
);
next();
});
Then in your template:
<script nonce="{{nonce}}">
window.previewMode = true;
</script>
4. It reduces “just this one script” creep
I’ve seen email tooling slowly turn into a marketing-tech landfill. CSP makes every new external dependency visible and deliberate.
Cons of CSP for email templates
It’s not free.
1. Inline styles are hard to avoid
Email HTML is built around inline styles and ugly table markup. If your preview renders the real email HTML, you may end up allowing 'unsafe-inline' for styles. That’s not ideal, but it’s often pragmatic.
2. WYSIWYG editors can be CSP-hostile
Some editors still depend on unsafe inline script patterns, eval, or broad asset access. Tightening CSP can turn into a negotiation with a bad dependency.
3. Merge field previews can create false confidence
A CSP on the preview page does not mean the delivered email is safe. It only means your web preview is somewhat contained.
That distinction matters.
Pros of CSP for delivered HTML emails
There are very few, and they come with caveats.
1. It may help in niche controlled environments
If you operate a closed ecosystem with your own webmail renderer or embedded mail client, you might be able to enforce CSP when displaying email content.
That’s not general email. That’s your product surface.
2. It can document intended restrictions
Even if clients ignore it, a policy can serve as internal documentation for how email HTML should behave. I wouldn’t call that a security win, but it can clarify engineering intent.
Cons of CSP for delivered HTML emails
This is where reality bites.
1. Email client support is unreliable
That’s the big one. You do not control Gmail, Outlook, Apple Mail, Yahoo Mail, or random enterprise clients. Many sanitize aggressively before rendering. Some strip the very place where policy declarations would live.
2. Email clients already disable or strip active content
JavaScript is commonly blocked regardless of CSP. So the security value you’d expect from script-src is often redundant or irrelevant.
3. Markup gets rewritten
Links may be wrapped for tracking. Images may be proxied. CSS may be altered. The final rendered content is often not what you sent.
A control that depends on exact browser behavior is weak in that environment.
4. You can’t treat email like a web page
This is the core mistake. HTML email looks like the web, but it behaves like a hostile legacy document format.
What to do instead for HTML email security
If your goal is secure HTML email, spend your effort here:
- sanitize HTML before send
- escape merge variables by default
- allowlist supported tags and attributes
- strip event handlers like
onclick - remove forms, scripts, iframes, and embedded objects
- restrict remote assets to approved domains during template creation
- generate plain-text alternatives
- test rendering across clients
A simple sanitizer pass in Node might look like this:
import sanitizeHtml from "sanitize-html";
const clean = sanitizeHtml(dirtyHtml, {
allowedTags: [
"table", "tr", "td", "tbody", "thead", "tfoot",
"p", "span", "div", "strong", "em", "a", "img",
"ul", "ol", "li", "br", "hr"
],
allowedAttributes: {
a: ["href", "title", "target"],
img: ["src", "alt", "width", "height"],
td: ["colspan", "rowspan", "align", "valign"],
"*": ["style"]
},
allowedSchemes: ["http", "https", "mailto"],
disallowedTagsMode: "discard"
});
That does more for real-world email safety than pretending CSP will save you downstream.
My recommendation
For a developer audience, I’d keep the rule simple:
- Use CSP aggressively on email template editors, preview pages, and web-based rendering you control
- Do not rely on CSP as a security boundary for delivered HTML emails
- Use sanitization, escaping, and strict template rules for actual email content
If you want the official reference for policy behavior, use the CSP documentation from MDN and browser vendor docs. For policy syntax and examples, the official spec is the right source, and https://csp-examples.com is handy for working examples you can adapt.
CSP is excellent on the web. Email is not the web. Once you accept that, your security design gets a lot better.