I build a browser tool that opens mail archives locally. Before a file is parsed as a PST, MBOX or EML, a short sniff looks at the first bytes and, when they are not mail, the UI shows a hint for that kind.
For files sniffed as html, the hint used to cover only one explanation: the page a sharing or download link returns when it needs a sign-in or has expired.
What the commit records
A visitor dropped the same 1–10 MB file four times in a minute. Each time it sniffed as html. They left after seeing only the sign-in / expired-link explanation.
I did not open that visitor's file, and I did not watch the session. The size range, the four attempts, and the fact that they left come from the commit message. What I can confirm from the source is the hint they would have been shown, and how html is detected.
How a drop becomes html
The sniffer does not look at the file name. After skipping an optional UTF-8 BOM and leading whitespace, it checks for an ASCII, case-insensitive <!DOCTYPE html or <html:
function looksLikeHtml(bytes: Uint8Array, offset: number): boolean {
let i = offset;
while (
i < bytes.length &&
(bytes[i] === 0x20 || bytes[i] === 0x09 || bytes[i] === 0x0a || bytes[i] === 0x0d)
) {
i++;
}
const head = String.fromCharCode(...bytes.subarray(i, i + 14)).toLowerCase();
return head.startsWith("<!doctype html") || head.startsWith("<html");
}
Existing tests already pin three shapes: a <!DOCTYPE html> page titled "Sign in", a leading-whitespace <HTML> page that says "Link expired", and the same doctype behind a UTF-8 BOM. A stray <note>…</note> document is still text, not html. I read those assertions in tests/sniff.test.ts. I did not run the suite for this post.
That detection is doing its job. The miss was in the sentence that followed it.
The old hint named only one cause
case "html":
return "The content is a web page, which is what a sharing or download link returns instead of the file when it needs a sign-in or has expired. Download the archive again from the source and check the size matches.";
That is a real case: a dead link often yields a small HTML page, and "download it again and check the size" is the right next step. It is the wrong next step when the visitor already has the file they meant to drop, and that file is itself a web page.
The commit's reasoning is size. A sign-in or expiry page is typically a few kilobytes. A file in the 1–10 MB range is far more likely a page or a message the browser saved as .html / .htm. I am not treating that as a measurement I took; it is the reason written in the commit and in the source comment.
What changed — and what did not
The hint now adds the second case, still in the same html branch:
case "html":
return "The content is a web page, which is what a sharing or download link returns instead of the file when it needs a sign-in or has expired. Download the archive again from the source and check the size matches. A page or message saved from a browser (.html, .htm) is not a mail archive either: open it in the browser, or save the message from your mail program as .eml (in Gmail: ⋮ → Download message), which Mailward opens.";
Two things this change does not do, and I want to be exact about them:
- It does not branch on file size. Every drop sniffed as
htmlnow gets both explanations. Size is why the second sentence was added, not a condition in the code. - It does not open a saved
.htmlmessage as mail. The hint says the opposite: that file is not a mail archive. The suggested path is to open the page in a browser, or to export the message as.eml, which this tool does open.
The new test assertions only check that the html hint contains "saved from a browser" and ".eml", alongside the older "web page" and "Download the archive again" checks. They do not assert a size threshold, because there isn't one.
Takeaways
- A content sniff can be right and the hint still wrong, if the hint names only the cause you thought of first.
- Expired-link HTML and a browser-saved page share the same magic bytes. If you mention one, mention the other.
- If size is only a reason for writing the copy, say so in a comment; do not imply the UI classifies by size unless the code does.
- When the file is the thing the visitor saved, "download it again" is a dead end. Tell them what the file actually is, and what format would open.
The format the hint points at is an .eml file. The page that opens one is the EML viewer. Disclosure: I built it.
Source note (2026-11-10): I read Mailward commit b8673f0 (AuthorDate 2026-11-10 09:20:25 +0800), the current src/lib/sniff.ts (looksLikeHtml, the html branch of renameHintFor), and the related assertions in tests/sniff.test.ts on the Mac. I did not run the test suite, did not open the visitor's file, and did not re-measure the session.
Top comments (0)