Skip to main content

Email Obfuscation: How to Hide Your Email From Scraper Bots

Hide your email from harvesting bots with HTML entities, JavaScript link assembly, CSS reversal, and [at]/[dot] text. Practical tradeoffs and a worked example.

Published By Li Lei
#email obfuscation #hide email #anti-spam #web scraping #privacy

Email Obfuscation: How to Hide Your Email From Scraper Bots

Put your address on a public page as a plain mailto: link and the clock starts ticking. Within hours, a harvester has crawled the page, run a regular expression like name@domain.tld over the raw HTML, matched me@example.com on the first pass, and dropped it into a spam list. The bot never read your copy or understood your site. It scanned the source text for an @ sandwiched between word characters and a dot, and your address fit the shape.

Email obfuscation is the answer to that pattern match. The idea is simple: make sure the literal string me@example.com never appears in the source a bot reads, while a real browser or a real human still ends up with a working address. Below are the four techniques that actually do this, the tradeoff each one asks you to accept, and why no single method is the whole story.

Why a plain address gets harvested

A spam harvester is not clever. It fetches pages, ignores rendering, and runs a regex over the bytes. That is its entire job, repeated billions of times. The literal text me@example.com matches instantly, so the cheapest possible defense is also the most effective in bulk: break the literal string so the regex finds nothing.

Everything that follows is a way to keep the assembled address out of the static markup. The methods differ in how hard they are to reverse and in how much friction they add for a genuine visitor. If you want to confirm how a scraper sees a page, run its source through an email address extractor — if your obfuscated version comes back empty there, the naive bots will come up empty too.

HTML entity encoding

Browsers decode HTML entities before they paint, but a regex run over the raw source does not. That gap is the whole trick. Rewrite each character as its decimal entity (d) or hex entity (d) and the literal address vanishes from the source while the page still renders normally.

A worked example. The address me@example.com becomes:

<a href="mailto:&#109;&#101;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;">email me</a>

A browser turns that back into a clickable me@example.com link. A regex looking for @ sees only &#64; — no match. You can mix decimal and hex, and you do not have to encode every character; encoding just the @ and the . already breaks the most common patterns. If you want to encode arbitrary text this way for any purpose, an HTML entities encoder does the same conversion outside the email context.

The tradeoff: entity encoding stops the lazy majority but not a scraper that decodes entities before matching. It is excellent against low-effort bots and useless against one that bothers to normalize the source first.

JavaScript link assembly and ROT13

If the address is never written into the static markup at all — only assembled when JavaScript runs — then a scraper reading the raw HTML has nothing to find. The most robust version stores the address scrambled and un-scrambles it at runtime.

ROT13 is a convenient scramble because it is reversible with no key. The address me@example.com stored as ROT13 reads zr@rknzcyr.pbz, which has no recognizable @-and-domain shape. A tiny inline script un-rotates the string and writes a live mailto: link into the page. The visitor clicks a normal email link; the bot reading the source sees only zr@rknzcyr.pbz with no pattern to grab. (If you are curious how the scramble itself works, a standalone ROT13 encoder lets you round-trip any string.)

This is the strongest of the four, because the assembled address only exists after a browser executes JavaScript — something most bulk harvesters do not do. The tradeoff: it needs script to run, so it breaks in any context that strips inline JavaScript, and a visitor with scripts disabled sees nothing.

The [at]/[dot] text method

Some places will not let you run HTML or script at all — a forum post, a PDF, a printed slide, a tweet. There you fall back to the oldest trick: write the address as readable text with the symbols spelled out. john (at) acme (dot) co instead of john@acme.co.

A human parses it without a second thought. A bulk scraper finds no @ and no mailto-shaped dot to regex against. The cost is real and worth naming: it is not a clickable link, so a visitor has to retype it, and any obvious convention ([at], (dot)) can be reversed by a scraper that specifically substitutes those tokens back. It is a low-tech option for low-tech contexts, not a fortress.

CSS reversal and other source-vs-render tricks

A subtler family relies on the fact that the source order and the rendered order can differ. Write the address backwards in the markup — moc.elpmaxe@em — and use unicode-bidi: bidi-override; direction: rtl; so the browser paints it forwards. The visible text reads correctly; the source string is reversed and matches no email regex. Variations hide a display:none decoy inside the address or split it across spans the scraper concatenates wrong.

These are clever and worth knowing, but I treat them as garnish. They confuse the source-vs-render boundary, which is exactly the boundary a determined scraper learns to handle. They also tend to harm copy-paste and accessibility, so the human cost can outweigh the bot resistance.

The tradeoff, and what I actually ship

Here is the honest center of all of this: bot resistance and human readability pull against each other, and no method is fully scraper-proof. Encoding the address as HTML entities or assembling it with JavaScript hides it from simple scrapers while a browser still shows it correctly — but a harvester that decodes entities or runs scripts can still get through. You are not building an impenetrable wall. You are raising the cost of harvesting above what a bulk operation will pay, which is enough to keep your inbox out of the cheap spam lists that fill it.

When I add a contact email to a page I control, I reach for the combination: a JavaScript-built link so the address is assembled at runtime and never sits in the static source, layered with entity encoding underneath. That pairing — runtime assembly plus encoding — is what the ROT13-plus-JS option produces, and in my experience it stops essentially all of the automated harvesting while keeping a normal clickable link for real visitors. Where I cannot run script, I drop to the readable (at)/(dot) text and accept that a human has to retype it.

One discipline matters more than the method: scrub every copy of the address on the page. Obfuscating the visible link does nothing if a footer, a JSON-LD block, or a meta tag still carries the plain string. Bots scan the whole document and happily take the easy one. Before you publish, confirm the address you do want — say, the one in your config or a signup form — is well-formed with an email validator, and then make sure no plain copy of it is left anywhere a crawler can read.

You can generate all four forms, side by side with a live preview, in the email obfuscator. Everything runs in your browser tab — the address you paste is never uploaded, logged, or sent anywhere, which is the whole point of a tool meant to keep your email out of the places that get crawled.


Made by Toolora · Updated 2026-06-13