Every attempt to make end-to-end encrypted email mainstream has died on the same hill: the client.
PGP plugins that break on every update. Browser extensions that only work in one webmail. "Secure email" services that make you abandon your mail client, your provider, and your address all at once. Thirty years in, the result is that approximately nobody encrypts email.
VESmail takes a different route: don't touch the client at all. It's a pair of transparent proxies — IMAP4rev1 and ESMTP — that sit between the mail client you already use and the provider you already use. Point Thunderbird or Apple Mail at the proxy instead of at your provider, and mail is encrypted on the way out and decrypted on the way in. Your provider only ever stores ciphertext. No plugin, no new app, no new address.
It's open source (Apache 2.0), written in C, and it works because of a pile of unglamorous protocol engineering that I want to talk about — because the crypto turned out to be the easy part.
The architecture
[your mail client] --IMAP/SMTP--> [VESmail proxy] --IMAP/SMTP--> [your provider]
Each proxy connects to the provider's server, relays commands and responses, and transforms messages in flight: outgoing mail (SMTP DATA, IMAP APPEND) gets encrypted, incoming mail (IMAP FETCH) gets decrypted.
To be clear about the trust model up front: the proxy sees plaintext, so it's part of the client side of the boundary. You run it where you'd run the client — localhost, your LAN, a box you control. It also runs fine as a multi-host daemon (per-SNI configs, graceful reloads, a guard process that restarts workers), but whoever operates that host is inside your trust boundary, same as your client machine.
The profile
There's a bootstrap question hiding in the architecture diagram: the proxy has to connect to your IMAP and SMTP servers, with your provider credentials — but all a mail client ever hands it is a username and a password.
VESmail resolves it with a profile: an end-to-end encrypted JSON object stored in VES that holds everything needed to reach your provider — IMAP and SMTP host and port, TLS level, SASL mechanism (PLAIN, LOGIN, XOAUTH2), your login and password at the provider — plus per-profile policy we'll get to below. It travels as encrypted metadata on a vault item named after your address, decryptable only by your VES keychain: the VES service stores your provider credentials but cannot read them.
So the login your client sends to the proxy is just: username = your email address, password = your VES App Key, the key that unlocks your VESmail vault. From those two strings the proxy derives everything else — unlocks the vault, decrypts the profile, and dials your provider with the credentials inside (holding them in memory for the session; it has to log in upstream, which is the same client-side trust boundary as before). Your provider password never appears in your mail client's settings at all.
That inverts the usual configuration dance. The client is configured once — proxy host, address, App Key — and everything about the provider lives behind it: change your provider password, switch to XOAUTH2, even move to a different provider, and you edit the profile (in the browser Profile Manager, or headlessly with the ves CLI) without touching the client.
Profiles can also be plural. A suffix on the login username selects one — you@example.com!work logs in with the work profile — so one address can carry several provider configurations, and a ##name login selects a shared profile that isn't namespaced to any address.
What an encrypted message looks like
The design constraint that shapes everything: the encrypted message must still be a valid RFC 5322 / MIME message, because it has to survive every mail server, spam filter, and mail client on Earth.
So VESmail rewrites the message instead of wrapping it. Each body part is replaced with an application/vnd.ves.encrypted part. Most headers are encrypted too — each one travels as an X-VESmail-Header: line of base64 ciphertext, leaving only what's needed for transit in the clear. And plaintext banner parts are injected in a multipart/alternative container, so a recipient on a client that's never heard of VESmail still sees something sensible — a notice with a link — instead of a wall of base64.
Schematically:
From: alice@example.com
To: bob@example.com
Message-ID: <...> <- doubles as the key ID
X-VESmail-Header: SGVsbG8gV29ybGQ... <- the real Subject, encrypted
Content-Type: multipart/alternative; boundary=...
--...
Content-Type: text/plain <- injected banner for non-VESmail clients
This message is encrypted with VESmail ...
--...
Content-Type: application/vnd.ves.encrypted
<ciphertext of the original body part>
Decryption reverses all of it — restores the headers, restores the body parts, strips the injected banner — and hands your client a message all but identical to the original. Your client never knows anything happened.
One detail I like: the Message-ID doubles as the encryption key's identifier (in the RFC 2392 spirit of message-ID-as-URI). Key lookup is simply "the key for this Message-ID"; if a message shows up without one, VESmail mints it.
Key exchange without a key ceremony
Each message gets a fresh random key. That key is encrypted to every recipient's public key — fetched from VES, which acts as the key repository — and the wrapped copies are deposited back into VES. Each recipient later unwraps their copy with their own keychain. The VES service only ever handles wrapped keys; wrapping and unwrapping happen at the endpoints.
VES is also what makes the keys survivable: a lost device doesn't mean lost mail, because your keychain is recoverable through VESrecovery, a social-recovery scheme through friends you choose — instead of a provider reset button that quietly implies the provider could read your mail all along.
The recipient who isn't on it yet
The eternal chicken-and-egg of encrypted email: what do you do when a recipient has no key?
The SMTP proxy collects the RCPT TO: addresses and has to share the message key with each of them. When one of them isn't on VES yet, the behavior is a per-profile policy, and the four modes are an honest menu of the actual trade-offs:
- FALLBACK — if any recipient isn't on VES, send the whole message unencrypted. Maximum deliverability, minimum security.
- FAIL — reject recipients who aren't on VES. Maximum security, and your mail bounces.
- XCHG — onboard the recipient with a temporary VES key carried in the message headers; they get access as soon as they set up their VES PIN. A bad actor intercepting the email still can't decrypt it, because the temporary VES key only unlocks a temporary vault, which VES releases only after the recipient initiates their own vault and verifies an OTP code sent in a separate email.
- HIGH — onboard via a temp-key exchange that completes on the sender's proxy session or VES service worker; most secure, but the recipient may have to wait for it if the sender is offline.
There's no pretending the trade-off doesn't exist — you pick where you sit on it, per profile.
Stream encryption and decryption
The engine under the proxies (and the CLI) is a single-pass stream transformer: bytes in, bytes out, headers and MIME parts rewritten in flight. It never buffers a whole message, so a 50 MB attachment doesn't cost 50 MB of RAM. The only thing that ever waits in memory is headers — because nearly every hard problem in streaming MIME lives in the header block.
Start with the key ID. The Message-ID doubles as the key identifier, and some MTAs feel entitled to overwrite Message-ID in transit — which would orphan the key. So the encryptor writes the ID down twice: the authoritative copy goes into its own X-VESmail-ID: header, and Message-ID: itself is rewritten with a marker suffix appended (.m.ves.world), which flags the message as VESmail-encrypted and keeps the encrypted copy's ID distinct from the plaintext original's. If an MTA replaces the Message-ID, the key ID survives in X-VESmail-ID. References: gets the same suffix treatment on the way out and the reverse on the way in, so threading survives no matter which view of the conversation a client sees.
Headers also fold. An RFC 5322 header can continue across any number of lines, so the parser reassembles each folded header into one logical unit before classifying it — with a hard per-header safety cap, so a hostile message can't balloon memory. The encryptor folds its own output the same way: X-VESmail-Header: base64 is emitted in line-sized chunks with tab continuations, preserving the message's own line-ending convention. Nothing downstream can tell those headers weren't born that way.
Then there's ordering. The decryptor can't decrypt anything until it knows the key, and it doesn't know the key until the ID header arrives — which can be anywhere in the header block. So headers that can't be resolved yet — the X-VESmail-Header: ciphertext lines, anything whose handling depends on what part this is — are put on hold in a divert queue and flushed once the key is fetched from VES; at the latest, the blank line that ends the header block forces the verdict. Only headers ever wait. Body bytes flow straight through the cipher.
Everything the encryptor invents is labeled. The multipart/alternative wrapper, the banner parts, the encrypted payload — each carries an X-VESmail-Part: marker (alternative, banner, injected, body). On decryption those labels are what tell the engine to unwrap this, drop that, restore the other. It never guesses from content, so a legitimate part that happens to look like a banner is safe.
That ordering buys the property that matters most in a stream: every fallible step — key lookup, access check, header decryption — resolves while the headers are still on hold, before a single transformed byte has been emitted. If this account permanently can't decrypt this message (key withheld, access revoked), the engine switches to verbatim pass-through and the encrypted original streams through untouched, banners and all. What's left is the rare mid-body failure — a transient VES or network error halfway through a part — and there the engine aborts the stream outright, because half a message you've already sent can't be recalled, and a proxy that hands your client silently corrupted "plaintext" is worse than one that drops the connection.
The hardest part isn't the crypto — it's IMAP
Here's where the war stories live.
Streaming has a price: you don't know the exact size of a transformed MIME part until you've processed all of it. (In the proxy, objects over a configurable threshold are transformed as a synchronous stream instead of buffered.)
IMAP, meanwhile, has BODYSTRUCTURE — and clients expect it to state exact octet counts.
So VESmail answers BODYSTRUCTURE with an estimate until it has fully processed the message within the session. Most clients are fine with that. A few are not: give them an estimate that overshoots and they'll loop forever, issuing ranged FETCH requests past the actual end of content. VESmail detects that loop (it calls the condition "OOR"), pacifies the client by sending a string of spaces, and switches that message to CALC mode — computing exact sizes by fully decrypting first. You can also just force CALC in the profile and skip the dance — at a price: pre-reading each message costs extra time and traffic.
Then there are the providers. Yahoo and AOL strip every non-Content-* header from BODY[*.MIME] responses — which would delete the X-VESmail-Header lines carrying the encrypted headers. VESmail detects the mangling and works around it.
None of this is glamorous. All of it is invisible to the user — which is the entire point of the design.
The instant viewer
There's one more consumer of the encrypted stream: VESmail Now, a small HTTP server that ships with the proxies (vesmail now) and serves spooled encrypted mail for instant viewing.
Point the config at a spool directory and every outgoing encrypted message gets a ciphertext copy deposited there by the sending process itself; if the Now server runs elsewhere, you instead tell the SMTP proxy to blind-copy outgoing mail to a dedicated address and let a small delivery script hand the (already-encrypted) copy over. Either way, nothing plaintext is ever spooled — it's the same ciphertext the recipient got, capped at a configurable size.
With a spool configured, the injected banner grows one more line: a per-message link, the viewer URL with the Message-ID appended. A recipient with nothing installed clicks it and lands in the end-to-end encrypted viewer — a web page that fetches the ciphertext from the Now server and decrypts it in the browser, so the Now server never serves anything but ciphertext — unlocks with their VES account, and reads that one message. No proxy setup, no client reconfiguration.
This composes nicely with XCHG onboarding: the recipient who was minted a temporary VES key by the message itself can finish their VES setup and read the mail in the viewer, minutes after it was sent, without touching their mail client's settings. The VES invitation email for a new recipient points at the same viewer link — "you've been sent an encrypted email" and "here's how to read it right now" are the same click.
What stays visible
Honesty section, because encrypted email has a history of overclaiming:
- Metadata is not hidden. Envelope addresses, timestamps, message sizes — your provider still knows who mails whom and when. This is content encryption, not anonymity.
- Transit headers stay in the clear. Most headers are encrypted, but what's needed to route and thread mail is not.
- The proxy sees plaintext. It's a client-side component; run it somewhere you trust.
- FALLBACK mode means plaintext. If you choose deliverability over strictness, some mail goes out unencrypted, by your policy.
- Recipients on plain clients see the banner, not magic inline decryption.
Try it in two commands
The stream engine is also a standalone CLI, no proxy in the loop. After a one-time browser step to create the VES account (sign in at the Profile Manager, confirm the emailed code, pick a PIN):
vesmail encrypt -a you@example.com < message.eml > message.ves.eml
vesmail decrypt -a you@example.com < message.ves.eml > roundtrip.eml
The first run opens the libVES keystore sync dialog: it shows a one-time sync code and asks you to open VESvault in the browser where you set up the account, pick Add Another Browser/App, and — after entering your PIN there — select the code the CLI is showing. Your keychain then syncs over, end-to-end encrypted (the CLI mints a throwaway key for the transfer, so the service relays only ciphertext), and the dialog asks for a PIN to lock the synced keychain on this machine. For unattended runs, stage a PIN-less credential first with the ves CLI: a session (ves -a //vesmail/you@example.com/ -E sess,save, encrypt-only) or the full App Key (-E save, encrypt and decrypt — use with care).
Diff roundtrip.eml against the original — that's the whole promise in one round trip.
The proxies are one command each (vesmail imap, vesmail smtp) — or run both at once as a service with vesmail daemon — and everything from account creation to profile management can also be driven headlessly with the ves CLI — see docs/profile-cli.md.
Builds with plain ./configure && make on Linux, macOS, and Windows (MSYS2); the CI workflow shows the exact dependency chain.
- Source: github.com/vesvault/VESmail — Apache 2.0
- Project site: vesmail.email
VESmail is built on libVES — end-to-end encryption between users, with VESrecovery™ key recovery — by the team at VESvault. Protocol-archaeology questions and threat-model scrutiny both welcome.
Top comments (2)
Nice post :)
My question is: If VES's servers go down, can you still access/receive/decrypt your mail? Or does it become bricked ciphertext.
With email, we're already dependant on our domain staying active, our email provider being alive, our password/PGP keys being remembered, so most of us wouldn't want to add a forth failure point to the mix.
(and small suggestion, if you wanted your post to reach more audiences here on DEV: things which read like an ad, or have the tell-tail AI structure tend to get less traction)
Thanks — and it's the right question to ask.
Straight answer: mail delivery is unaffected (the proxy isn't in the MX path), but if VES is unreachable you can't decrypt VESmail-encrypted messages until it's back. Worst case you point your client directly at your provider and read everything except the encrypted ones — each of those still shows a plaintext banner, so you can at least see what's waiting. Nothing gets bricked, but it's an online-first design and I won't pretend otherwise.
The trade we're deliberately making: of the failure points you listed, the only one that destroys mail permanently is the lost key. A lapsed domain or a provider outage you can recover from; a lost PGP key you can't, and that's how most encrypted archives actually die. VES exists to make key loss recoverable, and the price of that is a service dependency. Whether that's a good trade depends on which failure you think you'll actually hit over ten years of mail.
And point taken on the tone — fair.