{"slug": "invisible-unicode-characters-in-email-how-to-find-the-ghosts-in-your-templates", "title": "Invisible Unicode Characters in Email: How to Find the Ghosts in Your Templates", "summary": "A developer has published a guide and a Node.js regex-based checker for detecting invisible Unicode characters—direction controls like U+202E and zero-width characters like U+200B—that can hitchhike into email templates via copy-paste and silently alter how text renders or how filters match. The guide notes the technique underlies the Trojan Source vulnerability tracked as CVE-2021-42574, and recommends running the check in CI while deliberately preserving U+200C and U+200D, which are load-bearing in Persian and other scripts.", "body_md": "Picture a Tuesday deploy. Marketing pastes the new welcome email copy from a shared doc into your template, you ship it, and everything looks perfect in the preview. Two days later a support ticket arrives: a customer's email client showed one line of the message in a strange order, and your keyword-based content filter in staging never flagged the word it was built to catch. You diff the copy. The diff is empty. Nothing is wrong. Except something is.\n\nWelcome to **invisible Unicode characters in email**: characters that take up a position in your string, draw nothing (or rearrange what's around them), and survive every copy-paste with the stealth of a ninja who also happens to be a lawyer.\n\nMost text never contains these, which is exactly why they surprise people.\n\n| Family | Examples | What it does | \n|---|---|---|\n| Direction controls | Right-to-left override (U+202E), left-to-right embedding (U+202A), isolates (U+2066 to U+2069) | Tell the renderer to lay out the following characters in a different direction | \n| Zero-width characters | Zero-width space (U+200B), word joiner (U+2060) | Occupy a position in the string but render as nothing | \n\nBoth have honest jobs in typesetting. The trouble starts when the text a reader sees and the text your code stores stop being the same string.\n\nThe classic demonstration is a file name. Put a right-to-left override in front of `gpj.exe` at the end of `invoice_`, and the name displays as `invoice_exe.jpg`. It looks like a picture. It's a program.\n\nIn 2021, security researchers showed the same trick making source code read one way to a reviewer and compile another way. That research, tracked as [CVE-2021-42574](https://nvd.nist.gov/vuln/detail/CVE-2021-42574), is why the technique is now widely called \"Trojan Source\".\n\nZero-width characters are quieter, but they cause similar headaches:\n\n`===` disagrees.\nAlmost nobody puts them there on purpose. They arrive by paste: from a word processor, a shared doc, a web page, a chat message, or an old template that was itself pasted from somewhere else. Nothing on screen shows them, so they hitchhike from campaign to campaign like a burr on a sock.\n\nIf you build or send email from code, that means your templates, your CMS fields and your copy pipeline are all possible entry points.\n\nMost editors and every language with a regex engine can search for them. This pattern matches the direction controls and the common zero-width characters:\n\n```\n[\\u200B\\u2060\\u202A-\\u202E\\u2066-\\u2069]\n```\n\nA quick Node check over a template string:\n\n``` js\nconst INVISIBLE = /[\\u200B\\u2060\\u202A-\\u202E\\u2066-\\u2069]/g;\n\nfunction findInvisible(text) {\n  return [...text.matchAll(INVISIBLE)].map(m => ({\n    index: m.index,\n    codepoint: 'U+' + m[0].codePointAt(0).toString(16).toUpperCase().padStart(4, '0'),\n  }));\n}\n\nconsole.log(findInvisible('Welcome\\u200B aboard'));\n// [ { index: 7, codepoint: 'U+200B' } ]\n```\n\nA hit in text that's meant to be plain English is almost always safe to delete. Run it in CI against your email templates and you'll catch the next paste before a customer does.\n\nHere's where the \"just delete all of it\" instinct goes wrong. Two invisible characters are load-bearing in real writing:\n\nThat's why the regex above deliberately leaves out U+200C and U+200D. Strip them and you haven't cleaned your Persian newsletter, you've misspelled it.\n\nAnd real right-to-left text (Arabic, Hebrew) doesn't need override characters at all. The standard HTML `dir` attribute on a block, with embedded left-to-right runs such as prices and URLs isolated, gets direction right without any invisible controls in the text.\n\nThis is the approach Email Digit takes, described in its guide to [invisible Unicode characters in email](https://emaildigit.com/guides/invisible-unicode-characters-in-email): when an email is rendered for sending, the text of paragraphs, quotes and table cells is cleaned. Direction controls, the zero-width space, the word joiner and a few other invisible formatting codes such as the soft hyphen are removed, while the joiner and non-joiner are kept.\n\nThe guide is also upfront about the limits, which are worth copying into your own mental model whatever tool you use:\n\n`dir=\"rtl\"` on blocks instead of override characters.\nIf a character is invisible and changes what a line means, it doesn't belong in an email. Your diff tool can't see it. Now your CI can.\n\n*I work on [Email Digit](https://emaildigit.com), a deliverability-first email platform. Sources: Email Digit's guide on invisible Unicode characters (read November 12, 2026) and the Trojan Source CVE record.*\n\n*This post was drafted with AI assistance and checked against the sources above.*", "url": "https://wpnews.pro/news/invisible-unicode-characters-in-email-how-to-find-the-ghosts-in-your-templates", "canonical_source": "https://dev.to/emaildigit/invisible-unicode-characters-in-email-how-to-find-the-ghosts-in-your-templates-32bb", "published_at": "2026-11-11 20:40:35+00:00", "updated_at": "2026-11-11 20:52:28.704350+00:00", "lang": "en", "topics": ["developer-tools", "ai-safety"], "entities": ["CVE-2021-42574", "Email Digit", "Node.js"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/invisible-unicode-characters-in-email-how-to-find-the-ghosts-in-your-templates", "markdown": "https://wpnews.pro/news/invisible-unicode-characters-in-email-how-to-find-the-ghosts-in-your-templates.md", "text": "https://wpnews.pro/news/invisible-unicode-characters-in-email-how-to-find-the-ghosts-in-your-templates.txt", "jsonld": "https://wpnews.pro/news/invisible-unicode-characters-in-email-how-to-find-the-ghosts-in-your-templates.jsonld"}}