How a message travels
Forwarding happens while the sending server is still connected. There is no queue on our side, so nothing of yours sits anywhere waiting to be sent.
-
It arrives
A sending server delivers to our MX over TLS. Addresses you have not added are refused before the message is sent.
-
We authenticate it
SPF, DKIM, DMARC and blocklists run on the message and decide whether to accept it.
-
We relay it unchanged
The original bytes go on to each destination, with the verdict added as a header.
-
Or the sender retries
If a destination cannot take it right now, the sending server is told to try again later.
Quick-start
You need an early-access account with the mailvault CLI signed in, and access to your domain’s DNS.
-
Claim the domain
Adding a domain prints the two records to publish: a TXT record that proves the domain is yours, and the MX record that sends its mail to us.
$ mailvault domain add example.com domain: example.com id: 3f1c9a2e-… status: pending Publish this DNS TXT record to prove the domain is yours: name: _mailvault.example.com value: mailvault-verify=… Publish this DNS MX record so mail reaches the server: name: example.com priority: 10 value: mx.mailvault.sh Then run: mailvault domain verify example.comThe MX record moves all of the domain’s mail to us. If the domain already receives mail somewhere else, add the aliases you need before you change it.
-
Verify it
Once the TXT record has propagated, verification marks the domain ready. Mail to a domain that is not verified yet is refused.
$ mailvault domain verify example.com domain: example.com status: verifiedDNS can take a while. Exit code
8means the record was not found yet, and7means the lookup itself failed; both are worth retrying, so a script can wait on them:$ while true; do mailvault domain verify example.com && break case $? in 7|8) sleep 60 ;; *) exit 1 ;; esac done -
Add an alias
Write the address the way you would send to it.
--tocan be repeated, or take a comma-separated list, to forward one address to several mailboxes.$ mailvault addr add hi@example.com --to you@gmail.com address: hi@example.com forwards to: you@gmail.com endpoints: id: 8b7d0c41-… # one address, two mailboxes $ mailvault addr add team@example.com --to ana@gmail.com,ben@proton.meSend a message to
hi@example.com. It reaches your mailbox while the sending server is still connected. -
Change it any time
Addresses change without touching DNS.
addr setreplaces only what you name and keeps the rest.$ mailvault addr set hi@example.com --to new@gmail.com $ mailvault addr set hi@example.com --disable # refuse its mail for now $ mailvault addr set hi@example.com --enable $ mailvault addr rm hi@example.com # asks first; --force skips it $ mailvault addr list --domain example.com
Reference
What we keep
Nothing. A message is relayed while the sending server waits on the line, and is neither written down, queued nor copied. If a destination refuses it for now, the sending server is told to retry, and holding the message stays its job.
Authenticating a message means reading it: mail is decrypted in our process so SPF, DKIM and DMARC can check it. What we promise is what happens after that, and it is the whole design.
What you receive
The message is relayed byte for byte, with two headers added above it. Because nothing below them changes, the sender’s DKIM signature still verifies at your mailbox, so a message the sender signed still passes DMARC there — even though its envelope sender is now ours and SPF no longer lines up.
| Header | What it tells you |
|---|---|
Received | The standard trace line: the server that delivered to us, and our hostname. |
Authentication-Results | Our SPF, DKIM, DMARC and blocklist verdicts. Any header claiming to be ours that arrived with the message is renamed X-Original-Authentication-Results, so it cannot pass for the real one. |
X-MailVault-Action | Present only as quarantine: the message looked doubtful but not bad enough to refuse. It is forwarded anyway, since there is no folder on our side to hold it — filter on this header in your mailbox. |
A message that fails outright is refused while the sender is connected, so it never reaches you and never leaves our servers.
Address rules
| Rule | Why |
|---|---|
| At most 10 destinations per address | Each one is a separate delivery made while the sender waits. |
| No destination on a domain you host here | It would forward back to us and loop. |
| Unknown and disabled addresses are refused | The sender hears it before the message is sent, and nothing is relayed. |
| One address per local part | Adding one that exists fails with exit code 5; change it with addr set. |
Bounces
Forwarded mail leaves with our bounce address as its envelope sender. If a destination later bounces a message it had accepted, that bounce comes back to us and goes no further, so the original sender does not hear about it. Keep destinations to mailboxes that exist; rewriting the sender so bounces can find their way back is on the roadmap.
Catch-alls
A catch-all, written as @example.com, does not forward to a mailbox. It accepts every local part anyone tries, and relaying those guesses would spend the sending reputation your aliases depend on. Add an alias for each address you use, or deliver the catch-all to your own endpoint.
Exit codes
Every command uses the same codes, and --output json prints machine-readable results and errors.
| Code | Meaning |
|---|---|
0 | Success. |
1 | Unexpected failure. |
2 | Bad flags, arguments or configuration; nothing was sent. |
3 | The API key was missing or refused. |
4 | No such domain, address or key. |
5 | It already exists. |
6 | The server rejected the input. |
7 | Network failure, server error or DNS outage. Retry. |
8 | The domain’s TXT record was not found yet. Retry. |