Release notes
What changed in each release.
This file is the only place release notes are written. The notes inside the package and the release page on zephyrab.com are both built from it, so there is one thing to edit and nothing to keep in step by hand.
Version numbers. A release is 0.1.0~git<sha>. The sha is the commit the server software was built from, so any file you download can be traced back to the source that made it.
Packaging and documentation are sometimes finished after that commit and ship in the same release — the notes below say when that happened. The server binaries always match the sha in the version.
How to read an entry. Upgrade notes come first, because that is the part you may have to act on. A release with nothing to act on has no such heading. Fixed entries describe what went wrong, not just that it is better now — a fix you cannot recognise in your own system is not much help.
If you are editing this file. Each release starts with ## <version> — <date>. The build refuses to package a release that has no matching section. Use the headings below and leave out any that would be empty.
0.1.0~gitbafebae2026-09-12 (current)0.1.0~gitc03c87d2026-08-310.1.0~git5e1d3b42026-08-250.1.0~gitd8fc1d72026-08-250.1.0~git091b5452026-08-240.1.0~gitaa1d20d2026-08-240.1.0~git6979cfd2026-08-23
0.1.0~gitbafebae
Released 2026-09-12
The Community edition now hosts one mail domain. Mailboxes stay unlimited and always will. An administrator whose identity lives in a single sign-on provider can now sign in to the admin API without a ZephyrAB password.
Upgrade notes
Community deployments: one mail domain from this release on. If you run without a licence file and host a single mail domain — which is what running your own organisation's mail looks like — nothing changes for you.
If you already host more than one, nothing you have breaks: every domain already registered keeps receiving, keeps serving its DNS, and keeps re-verifying. The limit applies only to adding a domain. The server declines the next one with a sentence saying what the limit is and what to do about it.
Mailboxes are not capped, on any edition. A seat cap measures how big a deployment is; this measures who it serves. One organisation needs one mail domain, and a deployment carrying several is almost always running mail on other people's behalf, which is the tier the licence is for. A company with one domain and fifty thousand mailboxes runs free for ever, deliberately.
To check where you stand before upgrading:
GET /admin/api/licensereportsusage.domainsagainsteffectiveLimits.domains, and the console's Health & Security page shows the same two numbers.
Changed
- Community: one mail domain, one cell, unlimited mailboxes. Cells were already limited to one. The refusal is the edition's own sentence rather than a licence's — a deployment with no licence file is not told its licence is short.
- Re-registering a domain you already have is never refused, at any edition and in any licence state. This is also what pressing "verify DNS" writes through, so that keeps working when you are at your limit.
Added
Sign in to the admin API with an identity token (RFC 7523). Where a deployment uses single sign-on, an administrator no longer needs a separate ZephyrAB password just to get an admin token. Post the provider's identity token to
/admin/api/oauth/tokenwithgrant_type=urn:ietf:params:oauth:grant-type:jwt-bearer; the server verifies it the same way it verifies one on IMAP or JMAP, and mints its own short-lived token. Authority still comes from your delegations, read on every request — revoking a grant takes effect on the next call, not when the provider's token expires.The shipped web console still signs in with a password, so on a federated deployment it is the one client that still needs one. Obtaining an identity token needs a browser redirect flow, which belongs to whoever runs the provider; an integrator with their own front end has everything they need.
Notes
- The pricing page, the admin manual and the licence documentation were updated with the edition change, and the build now refuses a pricing page that describes a tier the server does not enforce — in either direction.
- The packaged server binaries are built from
bafebae. The website changes in this release (pricing, navigation, a page-wide restyle) were finished after that commit and are not part of the download.
0.1.0~gitc03c87d
Released 2026-08-31
Mail now signs as the domain it is from, so a platform hosting several organisations no longer fails their DMARC. Blind copies are stripped on the other sending door as well. Two defects fixed, one of which reported a misconfigured domain as healthy.
Upgrade notes
If you host mail for more than one domain, read this. Until now every outgoing message was signed with one key, whichever domain it came from. The server now signs as the sender's own domain when it holds a key for it. For domains whose DNS the server manages this needs nothing from you — the key exists and its record is already published.
For a domain whose DNS is somewhere else, you have one thing to do: publish its DKIM record. Ask the server what to publish —
zephyrab dns-check --domain agency.example— publish the DKIM line it prints at your own DNS provider, and that is the whole task. There is no second command confirming you did it: the server checks the domain's DNS itself, and when it sees its own key published there it starts signing as that domain from then on. Re-run
dns-checkto watch it happen.The record only appears once a key exists, and the first time you ask for a domain that has none, one is generated. Generating is not publishing and not signing — for a domain whose DNS you host, the key simply sits here until you choose to publish it.
Nothing signs as a domain until its key is seen in that domain's own DNS, and that is the important half. Signing with a key nobody can look up makes every message from the domain fail verification, which is worse than not signing at all — so until the record is out there, mail keeps going out under the platform key exactly as it did before. If somebody later removes the record, the server notices and goes back to the platform key rather than signing into a hole.
Nothing changes for a single-domain deployment. One domain, one key, same behaviour as before.
The startup line now names the rule in force, so you can see which domains will sign as themselves without reading the configuration.
Added
Per-domain DKIM signing. A message from
juan@agency.exampleis signedd=agency.example, notd=yourplatform.example.This is not cosmetic. DMARC checks that the signing domain lines up with the address in the From line of the message, so a message from one domain signed by another fails alignment — and a receiver that has been told to quarantine or reject on failure will do so. On a platform hosting several organisations, one key meant every organisation but one was mis-signed.
The domain is taken from the From header, because that is what the receiver checks. It is also checked against the account actually sending, so putting somebody else's domain in a From line does not get their signature — a message that fails that check is signed with the platform key, exactly as it was before.
Fixed
A domain publishing somebody else's DKIM key was reported as verified. The check asked whether a usable key was published at the selector, and a foreign key answers that yes. So the summary read
verifiedwhile every message the platform signed for that domain failed at the receiver — because the signature is checked against the published key, and the published key was not the platform's.Such a domain now reads
pending_verification, and the evidence names the mismatch. It is notfailed: the domain may be receiving mail perfectly. It affects only domains that both have a key here and publish a different one, so no other domain's status changes.Listing plans could report that there were no more pages when there were. A range read returns one batch, and the code took a short batch to mean it had reached the end without consulting the flag that says which it was. It showed up as a plan created moments earlier not appearing in the list.
The same mistake was fixed in one other place, and this one matters more: looking up an invite by id walked the whole set of invites, so a lookup that stopped early would report "no such invite" while the invite went on working. An invite is a credential, and the moment you most need to revoke one is the moment it has leaked.
Notes
Signing keys are held per domain and are never copied between hosts. Rotation is unchanged: mint the new selector, publish it, switch, retire the old one.
0.1.0~git5e1d3b4
Released 2026-08-25
One new JMAP method, and it is the one a client needs to send end-to-end encrypted mail.
Added
Email/import(RFC 8621 §4.8). Upload a message you built yourself, then turn it into a message in a mailbox. The stored bytes are the bytes you gave — nothing takes them apart and puts them back together.That matters for one case in particular. In an encrypted or signed message — PGP/MIME, or S/MIME — every byte is covered by the signature or the envelope. A server that rebuilds such a message from its parts does not produce the same message; it produces one that no longer verifies. Until now the only way to create a message over JMAP was
Email/set, which assembles one from a subject, a body and attachments, so a client that had to preserve the exact bytes could not use JMAP at all and had to fall back to SMTP.It is equally the way to copy mail in from somewhere else, where keeping the original exactly as it was is the whole job.
Everything the server works out from those bytes — which conversation a message belongs to, the search index, what a client sees in a message listing — is worked out the same way it is for mail that arrives normally. An imported message is an ordinary message afterwards.
Off nobody's switch: it is simply there, and needs no configuration.
Changed
- The API manual now documents
Email/import— the call, what it promises about your bytes, the two ways to name a blob, and the errors. It also says plainly what it does not do: importing the same blob twice stores it twice, and a message goes into one mailbox rather than several.
Fixed
- A note in the source listed five things as unsupported that had all been built: conversation threading, sending mail over JMAP, uploading and downloading attachments, chained requests, and importing. A list of what a product cannot do is read by people deciding whether to use it, so it is worth more short and true than long and cautious.
Notes
No other server code changed. If you are not writing a JMAP client, this release changes nothing for you.
0.1.0~gitd8fc1d7
Released 2026-08-25
Formatted mail in the webmail. No server source changed in this release — not a line of it, and no dependency or compiler version either. The server binaries are a fresh build of exactly the source that produced 0.1.0~git091b545, so they behave identically; their checksums differ, because a Rust release build is not byte-reproducible. What actually changed is the web client, the manuals and one line of the reference web-server configuration.
Upgrade notes
- If you serve the webmail behind a proxy you configured yourself, its Content-Security-Policy needs
img-src 'self' data: https:. With the oldimg-src 'self' data:the reader's Show images button does nothing — every remote image is refused, nothing errors, and nothing is logged. The message frame carries its own policy and inherits the page's on top, so the page can only take away from it. The reference configuration in the deployment payload is already correct; the administrator manual explains the reasoning. Nothing else about the page's policy should change. - Nothing to do if you use the installed presets.
Added
Messages sent as HTML are now shown formatted — headings, bold, links, tables, colours — with a Formatted / Plain text switch above each one. Before this, every message was shown as text, so a newsletter arrived as a wall of link text and an inline logo arrived as nothing.
The formatting is shown inside a sealed frame that cannot run code, cannot reach your mailbox and cannot reach the page around it. The sender's markup is filtered to a known-safe set on the way in, and the frame carries its own policy that permits almost nothing. Those are three separate defences, and each holds on its own.
Pictures loaded from the internet are blocked until the reader asks. Loading one tells the sender the message was opened and roughly when, so there is a line saying so and a Show images button. The choice is for that message only and does not carry to the next one — "I trust this sender" is a decision about one message, not about all mail forever. There is deliberately no setting to turn them on permanently.
Pictures the sender attached are shown straight away. They are already on the server and tell the sender nothing. They are fetched by the page and embedded in the frame, so the frame itself never holds your credential and never reaches the network.
Fixed
- Forwarding now carries the original's formatting and its inline images, so the recipient sees what you saw. Your own note is added as plain text above it.
Known issues
- The sender's own stylesheets are dropped (
<style>blocks). Only formatting attached directly to an element survives, so a heavily designed newsletter looks plainer here than in some other apps. - The message frame is a fixed height with its own scrollbar rather than growing to fit, and always has a white background — a message is not redrawn to match a dark theme.
- SVG images are never shown. An SVG is a document that can carry code, and it arrives looking like a picture.
0.1.0~git091b545
Released 2026-08-24
A bug-fix release. Please read the first upgrade note. If you are already running 0.1.0~gitaa1d20d, one of your domains may be damaged and nothing will have told you.
Upgrade notes
- Upgrading from
0.1.0~gitaa1d20dor earlier? Runzephyr-domain-reparent auditafterwards. Older versions could damage a domain record when the mail server restarted. The audit tells you if it happened to you. It only reads; nothing changes unless you ask. zephyrab doctornow checks for this too, so a normal health check will find it.- To repair a damaged domain, run:
zephyr-domain-reparent --domain <name> --to-tenant <id> --apply --confirm <name>It moves the domain back to the right tenant, along with its DNS records and its history, and fixes the links from its mailboxes. Mailboxes do not move. No mail is copied and there is no downtime. - A domain with DNSSEC or DKIM keys can only be moved on a machine that can read
ZEPHYR_DNS_MASTER_KEY_FILE. Without it the tool stops before changing anything. See Known issues for the reason.
Fixed
- Restarting the mail server could break a domain and take its DNS offline.
ZEPHYR_DKIM_DOMAINnames your real mail domain, and on every start the server tried to register that domain to its own built-in tenant. If you had put the domain in a tenant of your own, the two disagreed. The server then treated it as a brand-new domain: it got a new id, so every mailbox pointing at the old one was left dangling; its DNS setting reset, so ZephyrAB stopped answering for the whole zone — including the records for the nameservers themselves; and its encryption requirement and any "disabled" flag were cleared. The restart reported success and logged nothing. The server now leaves an existing domain alone. - A domain can no longer be moved between tenants by accident. Domain names are unique across the whole platform, so registering one that already exists under a different tenant is a claim on someone else's domain. That is now an error you can read instead of a silent replacement. To move a domain on purpose, use
zephyr-domain-reparent. - A DNS zone could stop being served with nothing in the log. Every zone that leaves service is now logged with the reason. If the server could not read its own settings, it now keeps serving the zone — not being able to check is not the same as being told to stop. A clean read that no longer lists the domain still retires it, so you can still take a domain out of service deliberately.
- "Keep serving the old zone" only lasted five minutes. Several parts of the server promise to keep serving the last good copy of a zone if it cannot be rebuilt. The cache threw it away after 300 seconds anyway, so the promise bought five minutes and then silence. Kept zones are now refreshed.
- Starter mailboxes no longer land in the wrong place.
me@andpostmaster@were created on whatever domain the server started with, even one you had already set up yourself. That is how a system ended up with a domain in one tenant and its mailboxes in another. They are now created only on a domain the server itself created, and they carry a domain id like every other mailbox.
Added
zephyr-domain-reparent— moves a domain to another tenant, taking its DNS records, its history and its mailbox links with it. It shows you what it would do and changes nothing until you add--applyand confirm the domain name. Itsauditmode reports problems without touching anything.- New
zephyrab doctorchecks for mailboxes pointing at a domain that no longer exists, and for mailboxes whose tenant does not match their domain's. Both are warnings: neither stops mail, but both make the admin views wrong. - DNS alert rules and a count of how many zones are being served.
- Release notes — this file. It ships inside the package at
/usr/share/doc/zephyrab/RELEASE-NOTES.mdand is published at https://zephyrab.com/releases.html.
Changed
ZEPHYR_DNS_ALSO_SERVEis now documented indocs/runbooks/dns-zone-serving.md, not just in the daemon's--help. It makes the server answer for a zone whatever its settings say, which is how you get a domain back if its configuration is lost.- The API manual now describes the request formats that previously had to be worked out by trial and error: DNS records need the full host name and are written one set at a time, mailboxes are created with
localPartanddomainId, changing a password needs aticketRef, and a webhook'ssubjectis a plain string like"platform"or"tenant:<uuid>".
Known issues
- Nothing watches DNS. There is no check on port 53 and the DNS server reports no metrics, so nothing in the bundled monitoring would have noticed the outage described above. The new alert rules say so themselves rather than suggesting cover they do not give.
zephyr-domain-reparenthas not been run on a live system. It is tested against a scratch database. Take a backup first and read the dry run.- DNSSEC and DKIM private keys are locked to the tenant that owns them, which is what stops a key being moved to the wrong domain. It also means moving a domain has to re-lock them, and that a domain with keys cannot be moved on a machine that cannot read the master key.
0.1.0~gitaa1d20d
Released 2026-08-24
The first release with webhooks, single sign-on and an admin API that works behind a load balancer. It is also the release that introduced the domain bug fixed above.
Added
- Webhooks. ZephyrAB can now call your server when something happens — seven events covering jobs, mailboxes and domains. Each call is signed so you can prove it came from us, and retried if your server is down. Off until you set
ZEPHYR_WEBHOOKS. A call may arrive more than once and calls may arrive out of order, so ignore repeats of the sameX-Webhook-Idand sort byoccurredAt. - Single sign-on. Point ZephyrAB at your identity provider and your users sign in with their existing account. Works for mail apps and for the self-service settings pages. There is deliberately no way for an administrator to mint a session for someone else's mailbox.
- Admin tokens work on any node. They are stored in the database instead of in one server's memory, so they survive a restart and you can put a load balancer in front of the admin API.
- Faster mail apps (JMAP result references). Searching and then fetching messages is now one request instead of two. Most JMAP client libraries do this by default.
- Licensing. A signed licence file the platform can read. Limits apply when you create things. Delivering mail, reading existing mail and every data-safety feature are never blocked, whatever state the licence is in.
- The installer. Run
curl -fsSL https://zephyrab.com/install.sh | sh, thensudo zephyrab install. It asks questions, writes a plan you can review, checks the machine first, andzephyrab doctorproves the result afterwards. Multi-machine setups, upgrades and an offline bundle are included. - Three manuals — administrator, user and API — as a searchable web page, a Word document and a PDF, including a chapter on how many servers you need.
Fixed
- Domains created through the platform-wide route stored whatever tenant text the caller sent. They now store a tenant id, which is what everything else expects.
- The installer package contained no
zephyrabcommand, and carried whatever server files happened to be on the build machine — which could differ from the ones the release installed. - Several installer bugs that would each have broken a first multi-machine setup.
0.1.0~git6979cfd
Released 2026-08-23
The first public preview: the mail platform, the operator tools and the packaging, as Debian packages with a one-command installer. Detailed notes were not kept for this build. The changelog properly starts with the entry above.