---
title: "End-to-end encryption"
description: "OpenPGP keys made in your browser. Mail you send to another OpenEmail address can be sealed before it leaves the tab, and sealed mail addressed to you opens in the reading pane, decrypted on your machine. The keys are never ours to hand over."
url: "https://openemail.uk/docs/knowledge/trust/e2ee"
area: "Knowledge base"
category: "Privacy & ownership"
status: "live"
---

# End-to-end encryption

OpenPGP keys made in your browser. Mail you send to another OpenEmail address can be sealed before it leaves the tab, and sealed mail addressed to you opens in the reading pane, decrypted on your machine. The keys are never ours to hand over.

## Details

- Both halves are wired end to end. Composing to a recipient whose key is published seals the body in the browser before the request leaves it; the server is handed armour it cannot read, marks it `pgp-mime`, and builds a real `multipart/encrypted` message. Reading runs the same way in reverse: the ciphertext is fetched, decrypted in the tab, and rendered as ordinary mail.
- The algorithm is the one everybody else already uses: OpenPGP, through openpgp.js, and PGP/MIME on the wire. A v4 key on Curve25519, the same shape Proton issues and gpg produces with its default modern algorithm. So the mail this reads is in principle the same mail Thunderbird, gpg and Proton produce, and nothing here is a format of our own that would need unwinding later.
- In practice, though, it is OpenEmail to OpenEmail. There is no Web Key Directory, no keyserver lookup and no Autocrypt header parsing anywhere in this codebase: the only place a recipient’s key is ever found is our own directory, which holds keys published from this app by people whose address is on a domain hosted here. Nor is there a way to hand your key out. The app publishes your public half into that directory and offers no copy or export of it, so a correspondent on Thunderbird has no supported way to get it. Interoperating with the wider PGP world is a property of the format, not something the product does for you yet.
- Reading is not limited that way, because decryption does not need a directory. Any PGP/MIME or inline-PGP message that reaches this mailbox sealed to a key in this browser opens, whoever sent it and whatever client they used. That includes messages sealed to a key you have since rotated away from: retired keys stay in the keyring and are tried alongside the current one, so rotating does not cost you the mail you already received.
- Green means opened, and it is reachable no other way. The Details → Security row turns green only after a decrypt has actually returned plaintext in this tab, never from a field on the message, never from the fact that an encrypted envelope arrived. It reads “Encrypted end-to-end. Opened with your key in this browser”. Everything short of that has its own sentence rather than a shared one: still looking, key locked, sealed to a key this browser does not hold, could not be opened, no key here at all, and this browser would not let us look. “We could not check” and “you do not have the key” are different statements and the row makes you read which one you got.
- S/MIME still cannot be opened. It is CMS under an X.509 certificate, openpgp.js cannot touch it, and there is no certificate store in the product to hold the key if it could, so an S/MIME message says OpenEmail cannot open it, and is not offered an unlock that would do nothing.
- The private half is generated in your browser and never leaves it: not encrypted, not inside a backup, not in a support tool. The server is never sent it, so there is nothing here to hand over, subpoena or leak. It is stored passphrase-locked in an IndexedDB database of its own, openemail-keyring, deliberately out of reach of the “clear your cache” advice and the debug console’s reset: both wipe the query cache, and a key stored alongside it would make routine support advice permanently destroy every encrypted message the account had ever received. Deleting your account does delete it, because there the mail is going too. Unlocking it holds it in memory for 15 minutes of inactivity and 8 hours at the outside, after which reading the next sealed message asks for the passphrase again.
- There is no escrow and no recovery, and that is permanent rather than unbuilt. Your passphrase is the only way in; forget it and every message anyone sealed to you stays on our servers as ciphertext nobody can read, us included. The mail is gone, and no amount of asking recovers it. The enrolment screen says that before the first key exists, behind a checkbox you have to tick, and the backup file is mandatory, and the Done button stays disabled until you have downloaded it. That file is written without the extra at-rest hardening this browser uses, on purpose, so it still imports into older gpg builds: a backup you cannot open somewhere else is not a backup. The key also lives in one browser on one device, and a phone or a second laptop has nothing until you import that file there.
- The directory holds public keys and nothing else, and a key belongs to a person rather than to a mailbox, since one attached to an address would have to be copied between everyone that address is shared with, which is escrow under another name. Looking one up needs a signed-in session and permission to send, never a public endpoint, because an open address probe is an oracle telling anyone which addresses are live mailboxes here. It answers identically for “we do not host that” and “we host it and nobody has published a key”, since telling those apart just moves the oracle behind a login instead of removing it. And a key stops being handed out the moment its owner loses access to the address, rather than when somebody remembers to revoke it.
- Every byte is sealed in the browser at the moment you write it, which is what makes the delayed sends work at all: a scheduled message or one sitting in the undo window is stored as ciphertext and dispatched later by a queue that holds no key and can read none of it. A recipient whose directory lookup FAILED blocks the send rather than being quietly treated as having no key. And a sealed message only ever goes out over a path that carries a message whole. An outbound path that takes an HTML body instead would post the armor as the visible text and report a success, so a send that would land there is refused before a byte leaves rather than after.
- What sealing will cost, measured rather than guessed: the armor is about 1.86 times the raw bytes, so roughly 2.7 MB of attachments fits the 5 MB the send path allows, and the composer refuses a payload over that before spending seconds encrypting one the transport would reject. Encrypted mail reports no opens and no clicks, because per-recipient tracking works by varying the body per person and one sealed block cannot vary, and a rewritten link is a link we can read, which is the opposite of the claim. An encrypted send can never be deduplicated by an idempotency key either: a fresh session key makes the ciphertext different bytes on every attempt, so a retry never fingerprints like its original.
- Scheduling seals at compose time, not at send time. The composer builds the ciphertext against the keys its recipients hold on the day you write it, not the day the message would leave, the rule this product already applies to templates and translations, where a scheduled message carries what you approved rather than whatever changed afterwards. The difference is that a stale template is merely out of date and a stale key is unreadable, so the composer names the date in words rather than offering a caveat: “Sealed now, sent on …. Everyone on it will need the key they have today.” The refusal that guards the other half is landed but has never fired, because no such message can exist yet: if one did, editing it afterwards would be refused rather than quietly allowed, since patching the body would write plaintext over the ciphertext and changing the recipients would change who it was sealed to, and either one delivers in the clear while every screen still calls it encrypted.
- Two things the composer will not offer at all, and says so rather than failing at the wire. A template is rendered on the server from a published version, so there is nothing in this browser to seal. A reply quotes the conversation underneath it and that quote is appended after the body would be sealed, leaving a readable copy of the whole thread sitting outside the seal, so replies and forwards cannot be encrypted until the quoted history is folded inside the ciphertext.
- None of this hides your subject line, who you wrote to, or when. The subject travels in the clear on a sealed message exactly as it does on any other, and the reading pane says so on the message itself: “The subject and the addresses travelled in the clear; this text did not.” PGP covers the body and no implementation of it covers the rest. Drafts are unsealed too, because autosave keeps writing plaintext to your mailbox while you compose, because silently not saving would lose work, and the padlock discloses it in words.
- Recognising mail that arrives sealed came first and still holds. PGP/MIME, inline PGP armor and S/MIME used to render as an empty body with two meaningless attachments, because the parser only treats plain text and HTML as readable and dropped the rest into the file strip. Those parts are now identified, the protocol furniture is kept out of the attachment list, the ciphertext is stored intact, which is what the reader decrypts from rather than fetching anything new, and a message nobody here can open still says so plainly.
- A signed message is treated as a separate thing, because it is. The body of one is readable, so search, rules and everything else keep working on it; it is never gated behind a lock and never run through a decrypt. The row reports “Signed by the sender. Signature not checked”, dimmed, and it stays dimmed until something here can actually verify a signature, which nothing does yet, including on a message that decrypted successfully. Seeing that a message is sealed is not the same as having opened it, and opening it is not the same as knowing who sealed it.
- On an encrypted message the body is withheld from everything that would otherwise read it: the search snippet, the phishing scorer’s body pass, the AI-writing check, body conditions in rules, calendar invite import, and thread summaries. The authentication half of the phishing check still runs, because DMARC, DKIM and SPF are read off headers that ciphertext does not hide. Decrypting changes none of that. The plaintext exists only in the tab that opened it, so a sealed message stays unsearchable and stays out of the AI features even after you have read it, and translation is not offered on one. That is the cost of the claim, not an omission.
