HTML Encoding Email Addresses

May 28, 20266 min read

Publishing an email address on a public webpage is an open invitation to spam. Automated crawlers—often called email harvesters—scan millions of pages daily, extracting mailto links and plain-text addresses to add to spam databases. HTML entity encoding offers a lightweight obfuscation technique that makes addresses harder for bots to read while keeping them functional for human visitors.

How Email Harvesters Work

Spam bots crawl the web using patterns similar to search engine spiders but with a different purpose. They look for strings matching the email format (local@domain.tld) and mailto: hyperlinks. Most harvesters operate at scale, processing thousands of pages per minute with minimal computational overhead per address.

Basic harvesters use regular expressions to find email-like patterns in raw HTML:

import re
email_pattern = re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}')

More sophisticated crawlers render JavaScript and decode entities before scanning. However, many harvesters skip entity decoding to save processing time, given that decoding every page's HTML entities across billions of URLs is computationally expensive.

This asymmetry—bots skipping decoding for speed while browsers always decode for display—is the foundation of entity-based obfuscation.

Encoding Email Addresses with HTML Entities

The technique replaces characters in the email address with their numeric or named HTML entity equivalents. The browser decodes these entities before displaying the address or handling the mailto link, so users see and click the correct email.

For the address hello@example.com, encoded versions might look like:

<!-- Named entities -->
<a href="mailto:hello&#64;example&#46;com">hello&#64;example&#46;com</a>

<!-- Decimal numeric entities -->
<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;hello&#64;example&#46;com">
  hello&#64;example&#46;com
</a>

<!-- Hexadecimal numeric entities -->
<a href="&#x6D;&#x61;&#x69;&#x6C;&#x74;&#x6F;&#x3A;hello&#x40;example&#x2E;com">
  hello&#x40;example&#x2E;com
</a>

Each variation renders identically in the browser as a clickable hello@example.com link. The key characters to encode are @ (&#64; or &#x40;) and . (&#46; or &#x2E;), since these are the anchors for email-detecting regex patterns.

For stronger obfuscation, encode the entire address including the mailto: prefix using decimal or hex entities. The more characters you encode, the less likely a pattern-matching harvester will detect the address.

Combining Multiple Obfuscation Techniques

Relying on a single method leaves gaps. Combining techniques significantly reduces harvest success rates.

JavaScript generation. Write the email address dynamically at runtime so it never appears in the static HTML source:

<script>
  const user = 'hello';
  const domain = 'example.com';
  document.write(`<a href="mailto:${user}@${domain}">${user}@${domain}</a>`);
</script>
<noscript>
  <!-- Fallback for noscript users: show contact form link instead -->
  <a href="/contact">Contact us</a>
</script>

CSS text reversal. Display the address right-to-left using CSS while keeping the HTML reversed, so the source reads backwards:

<style>
  .obfuscated { direction: rtl; unicode-bidi: bidi-override; }
</style>
<span class="obfuscated">moc.elpmaxe@olleh</span>

Contact forms. The most effective approach is removing the email address from the page entirely and replacing it with a server-side contact form. This eliminates the harvesting vector altogether.

Research from independent studies shows that combining entity encoding with JavaScript generation blocks over 95% of harvesters, while any single technique alone blocks 60-80%.

Limitations and Trade-Offs

Entity encoding is a deterrent, not a guarantee. Advanced harvesters that render pages in a headless browser (like Puppeteer) will decode entities and extract the address. The technique raises the cost of harvesting your address, which is sufficient to deter most automated crawlers.

Accessibility is another consideration. Screen readers should handle entity-encoded content correctly since the browser decodes it before the accessibility tree is built. However, JavaScript-based obfuscation can fail for users with JavaScript disabled, so always provide a noscript fallback.

Finally, once an address appears in any harvested list, encoding the page version will not stop spam that originates from previously compromised lists. Obfuscation is preventive, not curative—it protects new addresses going forward.

To encode your own email addresses for obfuscation, use the HTML Encoder tool to convert characters into entity references quickly.