Blog

Our CI fails the build when our marketing lies

Every claim on this site has to name the test that proves it, or admit that nothing does. Here is what that caught, including one we wrote this week.

Privacy companies ask you to trust sentences. "Encrypted at rest." "We never read your mail." "Hosted in the EU." You can't check any of them from outside, and most of them are written by someone who has never read the code they describe.

We are a small email provider, and we make those sentences too. So we treat them like code: each one has to point at something that proves it, and the build goes red when the sentence and the proof drift apart. This post is how that works, what it has caught, and where it stops working.

The setup

There are three pieces, all in the same repository as the product.

1. An inventory of every checkable claim. A markdown file lists every concrete promise the marketing site makes: "sorts new mail into two piles", "IMAP on every plan", "your mail is never used to train a model". Positioning copy ("the calm inbox") is left out. Right now it holds 84 claims.

2. Every live claim must name its proof. Under each claim sits a marker like this:

- [x] [LIVE] AI drafts replies before you ask; draft waits until you hit send.
  <!-- proof: test=backend/tests/test_triage_never_sends_for_you.py::test_triage_offers_a_draft_and_a_separate_send -->

A script checks that the named test file exists and that the named test is in it. Delete the test and CI fails. The other allowed answer is proof: unproven, and that is deliberate: most companies have no automated proof for most of their claims, and pretending otherwise would be worse. But the number of unproven claims is ratcheted. It can go down; it cannot go up. A new claim can only be added by proving it or by paying off an old one. Today, 68 claims name a test and 15 are honestly marked unproven.

3. Every claim quotes the copy that makes it. The inventory quotes the exact sentence from each page, 163 quotes across 46 files. A second script fails the build if a quoted sentence disappears from the site. That closes the loop in the other direction: nobody can quietly reword a page and leave the inventory describing a promise we no longer make, or a promise we now make more strongly.

Claims about competitors are kept apart. We cannot prove what Proton or Fastmail do with a test in our repository, so those carry a link to the competitor's own documentation and the date someone last re-read it.

What it caught

Encrypted in transit and at rest

This was on the site, and it is the sentence every provider writes. When we went to attach a proof to it, we measured what was actually in storage. Attachments and uploaded files were encrypted under a key we hold, every sampled object. Raw mail bodies were not encrypted at all: our mail server can't send the encryption headers our object storage needs, and the storage has no server-side default to turn on.

Worse, one heading drew the conclusion out loud: mail stored in Europe, "so our hosts can’t read it". That was exactly backwards. Our host holds the plaintext.

The measurement itself nearly lied too. Our storage provider accepts an encryption key on a request for an object that was never encrypted, and answers 200 as if all is well. The first pass counted those 200s as "encrypted". What actually tells you is whether the response echoes the encryption algorithm back, and whether the same request without the key fails.

We changed the copy rather than pretend, and added a test that fails if the unscoped version returns in any of six languages.

The same claim, again, this week

While writing this post we found that our new security page said message bodies were not encrypted "beyond the encryption of the underlying storage". There is no such encryption; see above. About fifteen other places said bodies were "not separately encrypted", which implies the same thing more quietly. Our comparison with Fastmail called our position "the same shape" as theirs, when Fastmail encrypts its disks and we do not. The site now says plainly that message bodies are not encrypted at rest, and the build fails if the softer wording comes back. We wrote that sentence ourselves earlier this week, with this whole system in place. That is the point: the system doesn't stop people writing hopeful sentences, it stops them surviving.

Sorts every email

The triage feature sorts mail into "needs you" and "handled". The site said it sorted every email. The code triages mail as it arrives and, by design, never goes back over mail that arrived before the feature was switched on. So the claim was not lagging, it was unsatisfiable. It now says new mail.

"Learns from a single tap"

This one was a product bug, not a copy bug, and it only surfaced because we had to prove the claim. When you move a message from Handled to Needs you, that correction was saved and read back correctly. But it was only consulted by the language model, and the cheaper deterministic rules run first. For about three quarters of mail, the rules decided and the correction was never looked at. Someone who rescued a newsletter from Handled watched the rules put the next one straight back, forever. We fixed the product so a correction overrides the rules for that sender, and the claim now has a real test.

Never scanned

The footer on every page said "EU-hosted, never scanned, never sold". Our servers do read your mail: that is how sorting and search work, and the site says so elsewhere. What we don't do is scan it for ads. Every page now says that, and a test fails on an unscoped version of that line.

What it can't do

  • It checks the code and config, not production. CI has no production credentials, on purpose. A test can prove the code only ever calls EU AI providers; it cannot prove the running server hasn't drifted. Claims like the at-rest one were settled by measuring production by hand, and the test pins the copy to what we measured.
  • It only knows the claims someone wrote down. A sentence nobody adds to the inventory has no proof and no guard. Two false claims on two new pages this month (calendar sync we don't offer, and an audit log described more broadly than it is) were caught by human review before merge, not by a test. The tests were added afterwards.
  • A test can be too weak. "The page contains this sentence" proves the page, not the product. The useful tests trace the sentence to the code that makes it true, such as checking that the upload path really passes the encryption key, not just that the page says it does.
  • Fifteen claims are still unproven. They're marked, counted and can't grow, but they're not proven.

Why bother

Because the alternative is asking you to trust us, and "trust us" is the thing email privacy is supposed to remove. We are not end-to-end encrypted, and we say so, because an AI inbox has to read mail to sort it. If you need a provider that is technically unable to read your mail, Proton or Tuta will suit you better. If you are choosing a provider that can read your mail, the least it owes you is sentences that are true, and a way of catching the ones that aren't.

The security page lists what is and isn't encrypted, the mail-authentication records you can check yourself, and what we don't claim.

Questions

What is a claims inventory?

A list of every concrete, checkable promise a product's marketing makes, kept in the same repository as the code. Mailotte's names, under each claim, the automated test that proves it or says plainly that nothing does yet.

Is Mailotte encrypted at rest?

Partly. Attachments and stored files are encrypted at rest with AES-256 under a key Mailotte holds, not its hosting provider's. Message bodies are not encrypted at rest. Mail is encrypted in transit.

Is Mailotte end-to-end encrypted?

No. Its servers process mail to sort and search it. If you need a provider that cannot read your mail, choose an end-to-end encrypted one such as Proton Mail or Tuta.

Can tests prove what a company does in production?

Not on their own. They can pin the copy to the code and configuration that produce the behaviour. Claims about the running system still need someone to measure production, and the test then keeps the copy matched to what was measured.

Take your inbox back.

Private, smart, and finally yours.

Start free