Skip to main content
OxiMail evolves as a continuous line: the current release is v0.30.0, and the line receives functional and hardening batches continuously. An upgrade is a binary swap — schema migrations apply themselves at boot, transactionally, with no manual steps (operations). The full, per-change changelog is maintained in the server repository and will accompany its public opening; this page summarizes what has landed on the current line, newest first.

Current line — v0.30 (2026)

Early October
  • Federation between organisations, served end to end (ADR-124 Am. 8 and 9): a pairing now records how to reach the peer. oximail federation pair takes its endpoint (--endpoint), its TLS pins (--tls-pin) and, for a tunnel you laid yourself, a literal private address (--address), and pins only once the transport fingerprint the other administrator read out is confirmed (--transport-fingerprint). This node’s own endpoint and pins are declared in a new [federation] section, whose door is off by default. Every federated request is signed under the RFC 9421 profile of the federation draft: it names both realms, carries its nonce and the working key’s certificate, and is verified on its header fields before its content is read. A peer is found by its own _jmap-federation._tcp record, never by the clients’ _jmap._tcp, and only when that record is DNSSEC-signed; the wizard publishes it once federation is on (federation).
  • What a paired organisation can read (ADR-168): finding a time with someone at a paired organisation reads their busy time from their own server. Event/proposeTimes asks the peer that serves the attendee’s domain and uses the answer only once its signature verifies; a peer silent for four seconds puts the attendee in unreachableEmails, never free. The answering server gives merged busy time, never an event, and records a disclosure per person before it answers. The operator can share a Drive folder or file, for reading, with a paired organisation (oximail federation shares add, list, remove, dry run by default); a drive root is refused, so one share never opens a whole drive. The peer reads it through the federation door, bounded to the shared subtree, the content in signed blocks of 1 MiB, under a budget per class and per pairing; a withdrawn share is journaled and pushed to the reader. On the reading side, a mirror account (oximail federation mirror add) shows those folders in its Drive: the copy lives in the server’s memory only, never in this node’s database or on its disk, is at most mirror_staleness_secs old (120 s), refuses every write with mirror-read-only, and shows a named state file instead of an empty folder while the owner is unreachable. A mirrored file downloads block by block from the owner, behind an in-memory cache that never touches the disk (content_cache_secs, content_cache_mib, content_cache_file_mib) (federation, sharing).
  • A federation identity that survives a restore or a lost paper key (ADR-127 Am. 5 to 7): a peer that re-issues an epoch under another key is refused; a new working key takes an epoch above both its predecessor and the clock, so a box restored from an older database never reuses an epoch its peers have seen; a retired root’s paper key opens nothing any more, and oximail federation roots retire erases its escrow. An identity whose chain no longer verifies is re-certified with an enrolled sister’s paper key (oximail federation working-key repair), and oximail federation roots delete abandons one on purpose, after which every peer pairs again. A tenant’s federation jurisdiction is its declared country of establishment: oximail federation identity reads it out beside the fingerprints, a pairing records what was declared to each peer, and doctor says when the country has changed since (federation).
  • Full-text search tells the truth (ADR-167): an index that cannot be opened is no longer a silent fallback. doctor names it, oximail_search_index_state publishes it, and Email/query declares an answer given without it (fullTextIndexUnavailable). After an upgrade the index rebuilds itself on its own task, served in full meanwhile, and oximail reindex rebuilds it with the server stopped, about eight times faster than before. The index now holds the subject, the From, To and Cc addresses and the body apart: body looks in the body alone, text in all five, and a word in the subject or the sender weighs more. Past 10 000 matching messages, a total is a real count instead of the cap, a date-sorted list holds every match, and an excluded word or an OR is evaluated exactly. A word inside an AND, OR or NOT filter is looked up in the index rather than in the 256-character preview, and IMAP SEARCH BODY and SEARCH TEXT go through the same index, so Thunderbird, Apple Mail and Outlook find a word anywhere in the body. A search for punctuation no longer returns the whole mailbox, highlights mark the word found (reunion marks Réunion), and a destroyed note leaves the index even when the index was closed at the time (mail, operations).
  • A backup MX judged on the real client (ADR-166): mail a backup MX relays is now judged on the address of the client the backup served, with SPF, DMARC and DNSBL applied as for a direct client. The primary recognises its backups from the MX records of its own domains, with no setting, and never treats one as a trusted peer; the backup writes its own Received line before relaying, as RFC 5321 requires, so the primary can read that address. The backup MX wizard no longer generates or publishes a DKIM key for the domain (with a Cloudflare token it could replace the primary’s key and break its signatures), nor client SRV records, and doctor no longer asks a backup for the domain’s SPF and DKIM. The log line of an accepted message names the client it was judged on and the SPF, DKIM and DMARC verdicts (mail and SMTP, anti-spam).
  • DNS the box can trust and republish (ADR-165): the server resolves through the local validating resolver and believes a DNSSEC answer only once a probe has proven that resolver (oximail resolver check). A deploy refuses an unproven box, and DANE deliveries wait rather than leave unauthenticated (oximail dane suspend lifts this for a bounded time). The resolver configuration no longer denies a record just published for half an hour (oximail resolver ensure --apply rewrites an older one), and the wizard empties the local cache after publishing. oximail setup dns run again reads the zone first and never doubles a record (a second _mta-sts TXT used to leave the domain with no MTA-STS policy), runs without a terminal (--replace-spf, --replace-dmarc, --public-ipv4), and finds the Cloudflare zone of a delegated subdomain. doctor checks each mail domain against the real host name (oximail check-dns --hostname), and a DNSBL that refuses or cannot answer is no longer read as “not listed” (installation, operations).
  • Sign-in, bans and devices (ADR-031 Am. 1): an office behind one address is no longer banned because of one guest or one phone on an old password. fail2ban bans an address when five different identifiers fail from it within fifteen minutes, counted apart for mail, web and federation; an identifier that signed in from there in the last 30 days, or the same wrong password retried, does not count. IPv6 is counted by its /64, repeated bans climb from one hour to one day, a valid token still works from a banned address, and oximail ban list shows the class and the reason. A new app password is checked in one read and one HMAC instead of five Argon2 hashes, and existing ones keep working. A mail client on an app password sees the mailboxes shared with its user again, and every IMAP, SMTP, CalDAV and CardDAV sign-in line names the credential and the client address, so an administrator can tell one device from another. The Apple profile, the Mozilla autoconfig and the Microsoft Autodiscover escape every value they write instead of copying the request into their XML (anti-spam, legacy protocols).
  • A private key kept on the server, never readable by it: the owner of a personal account can keep a copy of their OpenPGP or S/MIME private key already protected by its passphrase (PrivateKeyEnvelope/get, PrivateKeyEnvelope/set, PrivateKeyEnvelope/changes). The server refuses one in clear, never decrypts it, and reveals, replaces or removes it only after a recent sign-in of the session. A missing proof answers stepUpRequired, so a client tells a curable refusal from a final one. An account without a passkey proves its password again on the open session (POST /auth/password/reauth) instead of signing in anew, and a password never counts as a passkey proof (mail).
  • Chat and calls: a call leaves its trace in its room, one call message per session (audio or video, duration, who answered, missed, declined), unread only for whoever missed it. ChatRoom.lastMessageCall lets a room list say “missed call” without loading a message, and the caller reads who did not answer (MeetSession.missed). A person can find the rooms they may ask to enter (ChatRoomDirectory/get and ChatRoomDirectory/query, announced by roomDirectory in the chat capability), with their name, description and member count only. ChatMessage/query reads an anchor as RFC 8620 says (anchorNotFound), a thread read from an anchor lists that thread alone, and a search answers the total it was asked for (chat, Meet).
  • Connections that end cleanly: at a restart the SMTP, IMAP and HTTPS listeners stop accepting, an idle session is told 421 4.3.2 or * BYE [UNAVAILABLE], one in a transaction finishes it, and the stop names the workers it was still waiting for. The SMTP five-minute limit is now an inactivity limit re-armed by every command (421 4.4.2 to a silent peer, one hour at most per session). Every accepted connection disables Nagle, which took 40 ms off each IMAP command on a box without a proxy. The server pings every WebSocket and closes one that answers nothing, so a sleeping phone gets its Web Push back, and a push patch says null when a property is cleared (operations).
  • Observability: where the time of a write goes (SQLite lock wait, lock hold, COMMIT, writes outside a transaction), IMAP latency by command and result, SMTP delay after the data by listener and reply class, the durability barrier, the pushes held back, the DNSBL answers by kind and the resolver’s trust are Prometheus series with closed labels, never an address or an account. An administrator sees, for each device of an account, whether it has a live push subscription and when its push service last accepted a notification, never the endpoint URL (operations).
  • And also: a Drive name Windows reserves (CON, NUL, aux.txt and the like) is refused at creation and rename, since the SMB share served it and Explorer could then neither open nor delete it; address autocompletion looks in the tenant’s country first; the end of an install says how a device gets its settings.
Last days of September
  • Backups for a box without a hypervisor: oximail backup --to writes an age-encrypted, incremental repository the box can write but never read, restorable with the age tool alone. Into an s3:// bucket it requires Object Lock and refuses a key that could delete or read (a bucket in GOVERNANCE mode gets a warning). A run can be held to a rate and a duration, sends large objects in parts and dates its snapshots by the repository’s clock, and a full verification leaves a dated receipt that doctor reads. oximail account import --from-backup brings one account back beside what it holds, notes included, and oximail chat restore-room --from-backup takes a repository. A backup plan nothing ever confirmed is a warning, not green. After a restore the delegated DKIM keys are replaced, and IMAP clients resynchronise instead of attaching a reused UID to another message (operations).
  • An erasure a restore does not undo: every completed erasure is recorded in a ledger that travels with the backups; a person a restore brought back is frozen, and oximail erasure replay erases them again. Copies of their mail that a colleague’s legal hold keeps are left as they are and redacted when the hold ends, and closing an account withdraws its card from the people directory and from every address book (compliance).
  • Filtering is Rules only, applied at delivery: mail filters are typed Rules (move by mailbox or by role, keywords, named headers, comparators). The SieveScript methods, the Sieve JMAP capability and ManageSieve are retired, and active stored scripts are imported into Rules at boot. Rules reply automatically and the vacation response is sent; a failing rule keeps the message in the Inbox; a rule whose target is destroyed goes inert; notifications stop at thirty an hour per account. Rule/test tests an unsaved rule or the whole set, Rule/apply names its messages and is in the audit trail, and its per-call bound is [rules] max_apply_emails_per_call (rules).
  • Forwarding guarded like a credential, and verified (ADR-162, ADR-163): setting a forward needs a recent passkey proof when the account has a passkey, and is refused with an app password. A password reset by an administrator disables every forwarding rule and cancels the copies still queued, and every forward set or removed is in the audit trail under a fingerprint of the address. Under [rules] forward_policy = "verified" a rule forwards only to an address whose holder has confirmed it: the account asks with ForwardConfirmation/set, the holder receives a code valid seven days, and ForwardConfirmation/confirm opens the destination. The code message names the controller, the countries where the address is processed, whom to write to and how to give the code back; an unanswered request is erased when its code expires, and the operator can withdraw and block an address at its holder’s request (rules).
  • Who is responsible, declared (ADR-164): a tenant declares its kind, legal name, address, country of establishment and typed contacts (oximail tenant identity set, legalIdentity on Tenant/set); the instance declares who operates it and where the data is processed (oximail admin instance identity set, at the console only). A field left empty inherits the instance’s, and doctor legal_identity names the missing fields, never their values. The texts sent to third parties name that controller, and an operator can install a national variant that adds the paragraphs naming its law after the worldwide text; none ships until a practitioner has validated one. Accounts get the tenant’s default language (multi-tenancy, compliance).
  • Standard JMAP clients get what the RFCs promise: a create answers the properties the server set (defaults, computed values, normalized addresses) on every type, as RFC 8620 §5.3 asks, and a whole object from Foo/get is a valid update patch. Email/get serves headers in message order and header:<Name> in all seven RFC 8621 forms, and textBody and htmlBody follow §4.1.4. EmailSubmission/set sends a draft a standard client created and answers typed refusals; Thread/changes reports created and destroyed conversations. A request can file a message in a mailbox created earlier in the same request (#creationId) or patch a record by its creation reference; alreadyExists names the existing record (existingId); a method outside using is unknownMethod. Every vendor capability is served under its URN and under its URL (https://oximail.ch/jmap/<name>), and a collection quiet for 90 days keeps its state instead of answering cannotCalculateChanges for good (JMAP core, mail).
  • IMAP, CalDAV and CardDAV conformance (measured with imaptest and real clients): \Draft is a permanent flag, IMAP4rev1 clients see \Recent, LIST follows LIST-EXTENDED and CREATE no longer subscribes. SORT follows RFC 5256 and RFC 5957, THREAD no longer keeps a parent that a message’s own References contradict, APPEND keeps its date, another session’s flag changes and expunges reach NOOP, and ENVELOPE writes address groups as the header did. A sender’s subject, address or file name can no longer write an IMAP response of its own, a command line has a ceiling, and IMAP holds at most 20 connections per account and address. Calendar clients can rename and recolour a calendar or a task list, CalDAV and CardDAV queries honour the component, the collation, the limit, the conditional headers and the depth, a recurring event without an end stays past its first year, and a deletion reaches the other devices (legacy protocols).
  • SMTP, DMARC, ARC and MTA-STS, closed: DATA ends only at <CRLF>.<CRLF>, so one transaction carries one message; the inbound door answers as RFC 5321 says, and postmaster@ and abuse@ reach the tenant on every one of its domains. A forged From: no longer passes DMARC and a message without a single author is refused; c=simple signatures verify, a domain with exactly ten SPF terms passes, and a signature whose l= leaves part of the body unsigned is dkim=policy. The ARC exception to DMARC honours only the sealers named in [spam] arc_trusted_sealers, empty by default, so a message cannot vouch for itself with a chain it sealed. MTA-STS matches a wildcard pattern on a whole label only. A failed DANE lookup can no longer come back as “no TLSA” through the host’s search list, and the DNS library update lets domains under TLDs signed with RSASHA1-NSEC3-SHA1 validate again. The mail ports refuse a TLS client asking for another protocol and cut a session after three unknown commands before authentication, and every listener bounds a TLS handshake to 10 seconds. Behind a local reverse proxy, OxiMail can serve HTTP on a Unix socket only the proxy’s group may open ([server] bind = "unix:<path>"). A message sent from a mail client no longer tells its recipients the workstation’s name and address (mail and SMTP, email authentication).
  • Accounts under attack: a passkey can no longer be enrolled from an app password or a password alone, sensitive acts need a passkey proof from the last ten minutes, and an administrator’s password reset closes every app password and passkey of the account. An account under attack is slowed down on every protocol, never locked, and a CalDAV or CardDAV client no longer costs six password hashes per request. A call composed inside another (Core/queryStream, push patches) passes the shared-account gate and leaves its row in the access log. Secrets kept under the tenant key are sealed bound to their field and row. The key directory never serves a revoked or expired OpenPGP key, and OpenPGP moves to rPGP 0.20, which bounds the decompression of an imported message. A chat client can no longer forge another member’s reaction or hide that a message was edited (compliance).
  • Sending at scale (ADR-157): outgoing mail waits in three classes, so a person’s message leaves ahead of a running campaign, even toward the same host, and the dispatcher runs the 16 delivery slots it was meant to instead of 4. An account can carry sending flows (oximail account flow create), each able to leave from its own address ([[smtp.egress_pool]], oximail account flow set-pool), judged by a single admission object that runs in shadow for now (doctor counts the days it agrees). The relay signs a client’s mail with the client’s own domain (oximail account signing-domain add, two CNAMEs to publish), and meets the large providers’ sender requirements: complaints count against the flow that sent, a flow pauses at a 0.3 % complaint rate, bulk mail needs a signed one-click unsubscribe and a domain with DMARC, and origination mail carries a Feedback-ID. A sending domain’s proof belongs to the tenant’s own domain, and outgoing mail leaves over IPv4 whenever the destination has one. A 250 after the data now means the message survives a power loss: a durability barrier runs before it on the doors named in [storage] durable_doors (outbound relay, bulk sending).
  • Dangerous attachments, observed first (ADR-155): executables, disk images, listed extensions, macros and unreadable archives can be refused before the 250, on port 25, on the backup MX’s queue and on submission, and the same policy covers web-client mail, chat attachments and uploads; imports and IMAP APPEND are traced, never refused. Legacy Office documents, Outlook messages and winmail.dat are read, and a Drive file whose content is an executable is served as application/octet-stream. A new box observes and counts before any rule is armed (attachment policy).
  • Mail, notifications and grants: an encrypted message carries the $encrypted keyword (oximail email backfill-encrypted marks those already stored) and no longer reports an S/MIME status of “unknown”. A preview is the message’s text, without the link annotations and image markers a converter wrote (oximail email reindex-bump --link-preview re-derives the stored ones), and SenderBrand/get gives the verified logo of an address without any outgoing request. A new-mail notification says whether the sender is a colleague, a contact or unknown, whether the mail is bulk and whether it answers one of your messages; “Later” on an alert rings again once at the chosen time; an incoming call can be declined and rings for 45 seconds on a device whose app is closed. An account grant (oximail account grant) now opens every object of its collection at its level, those created after it included, and never shares or administers (mail, CLI).
Late September
  • Received S/MIME signatures are verified (ADR-156): RFC 9219 as written, under urn:ietf:params:jmap:smimeverify, with smimeStatus, smimeStatusAtDelivery, smimeErrors, smimeVerifiedAt and three filters. Both verdicts are stored and re-evaluated at their deadlines (end of the certificate path, next CRL), so an expiry or a revocation reaches Email/changes. Revocation is checked by CRL, never OCSP. The trust store is the Mozilla email roots plus the CCADB intermediates, and the signer’s detail is smimeSigner under v2:mail. The server never decrypts: an encrypted message is verified by the client. Mail stored before the feature is marked by oximail smime backfill (signed messages, operator side).
  • OpenPGP keys found by the server: RecipientKey/lookup tries the recipient domain’s WKD, then the key server, and tells “not found” from “unreachable”; PgpKey/publish publishes one’s own key. Advertised under recipientkeys and switchable in [pgp_directory], it follows the rule that the browser only talks to its own origin (OpenPGP keys).
  • DANE that holds over time: an ACME renewal keeps the TLS key, so the 3 1 1 pin no longer moves every 60 to 90 days. oximail dane rotate changes the key on purpose, publishing the next pin before the switch. TLSA records can be published on your own DNS by RFC 2136 dynamic update (TSIG). The pins cover the SMTP ports only (a backup MX pins port 25 alone), and the key each pinned port actually serves is read on the wire by doctor and an hourly probe. A failed renewal keeps the valid certificate in service instead of swapping in a self-signed one. The boot refuses a resolver that cannot prove a denial of existence, and an unverifiable TLSA answer makes a destination unreachable rather than “no DANE”. Boxes that terminate TLS themselves offer HTTP/2 (TLS and ACME, DANE records).
  • Sending, the defects a relay review found: a relay or envelope-sender domain whose DNS proof was suspended, or not confirmed for 72 hours, now defers with 451 4.7.1 instead of authorizing forever or bouncing, and doctor sender_domain_proofs watches it. A submission without Date: or Message-ID: gets one from the server, and a second From: is refused and never signed. An internationalized From: domain is signed by its key. A message a rule forwards is signed with a hosted domain’s key only when its author was authenticated. Inbound DKIM checking is bounded (8 signatures, 30 seconds). A bounce report carries the original Message-ID again, so bounce processors can attribute it. A delivery report that does not come from the null sender changes nothing (mail and SMTP, outbound relay). ADR-157 records where this is going: mailboxes and relay on one server, with the sending decision taken at admission. It is a design, and only its first defect lots are served.
  • The departure of a collaborator, from the admin surface: suspend, send their mail to a colleague until a date, and give that colleague the reading of the mailbox (Principal/setAdmin {forwardTo, forwardUntil, mailboxGrantTo, grantUntil}). Set someone’s password behind a strict second factor (Principal/setPasswordAdmin), see and cut one device session or app password of another account, and follow an erasure request to its completion record (ErasureJob/queryAdmin). Each act is journaled under its subject (admin API).
  • The health of the box, readable over JMAP (ADR-154): a timer records the doctor round every 15 minutes, and HealthReport/query and HealthReport/get serve it. On a single-tenant box the tenant administrator reads it; on a box with several tenants, only an instance operator does, a role granted at the console alone (oximail admin instance grant) to an administrator with a second factor. doctor --json gives each check its family, its dated facts and, on a warning, the verb to type (the instance operator, the doctor round).
  • Organizations, accounts and credentials: an administrator declares which groups are the organization’s entities, the employers a profile card may cite (isEntity, oximail admin organization create, set-entity, list, set). A service account is declared, never deduced, and its grants carry no expiry. An app password can be read-only: JMAP only, with every write answering accountReadOnly. A mail app password can send through JMAP as it already did through SMTP. GET /health counts only the caller’s organization, and deleting an organization is refused while it holds an account or a blob (multi-tenancy).
  • The setup wizard finishes the job: full screen on a capable terminal, it measures the host, asks how the box will be backed up (a hypervisor snapshot confirmed by oximail backup mark, or a scheduled oximail backup), runs the recovery-key ceremony (shown once, typed back, the drill recorded; oximail key escrow --guided on an existing box), creates the /metrics token, watches the sending IP on blocklists and ends with a reachability round. The escrow now also carries the instance secret, so a box restored on new hardware is the same box. Community telemetry (ADR-153) asks first, with no default, and a box without an answer sends nothing; oximail telemetry enable, disable, status and purge change or inspect it (first boot, telemetry).
  • Configuration: the SRS and TURN secrets left the TOML for files (secret_key_file, turn_shared_secret_file; oximail config externalize-secrets moves them). Each organization has its own overrides through /admin/v1/tenants/{id}/config, and oximail config set-override refuses what that route refuses. The outbound budget is set per account. The six trashes (Drive, calendar, contacts, tasks, notes, chat) have a retention per organization (configuration).
  • Brand logos of external senders (BIMI): shown when the server has verified the sender’s mark certificate, with brandMarkType telling a registered mark (VMC) from a common-law one (CMC). A DKIM-signed BIMI-Selector is honoured, and oximail bimi backfill gives the logo to mail already in the box (configuration).
  • Chat: the body of a message or room is content; encryptedContent is no longer served nor accepted. A voice message’s transcript is computed once by the server (ChatMessage/transcribe, [chat] transcript_policy). Threads are followed explicitly and a reply notifies only the followers. A room’s notification preference is applied by the server, so it also holds for the push of a closed app. Read receipts can be switched off, reciprocally. A write can only cite a file its writer can read (chat).
  • Mail and search: a word is found by its beginning, and a relevance sort puts a word in the subject or the sender, and recent mail, ahead of old notifications that repeat it. The body of an attached message comes in the same answer (viaMessagePartId), Email/set accepts bodyStructure on create, and a control character in an address or a subject is the client’s error (invalidProperties), on mail and contacts alike. oximail email normalize-received-at repairs the dates an old import stored in the wrong format (mail).
  • Calendar: one recurrence form, the RFC 8984 object of a closed profile, one rule per event or task, with exclusion rules applied and monthly and yearly rules expanding as RFC 5545 says. DTEND becomes the duration on every door and comes back unchanged over CalDAV. Availability says when it could not see everything (complete, incompleteFor on Event/proposeTimes), and the booking page offers no slot on such a person. An incoming iTIP message is applied only by someone with authority over the event (calendar).
  • Every type declares what may be written to it: an unknown property is refused by name before the handler runs, on every type of the surface (JMAP v2). Every write records its change-log entry in its own transaction, so a failure rolls both back instead of leaving a change no sync client ever sees.
Mid-September
  • A shared calendar no longer leaks what confidentiality hides (ADR-152): sharing already reduced what a colleague receives of a private event and served nothing of a secret one, but search still looked at the real content, so a title filter answered yes/no and a time bound revealed the secret slots. “What this reader may see” is now declared once and rendered twice, as a SQL predicate for the surfaces that return ids and as a reduction for the ones that return objects, tied by an agreement test: a secret appears in no result set, no total and no page; a private stays visible by time or calendar and leaves the set as soon as a content filter is set; the owner loses nothing. The same rule reaches the nine CalDAV report sites, and the access log now carries two counts per read, how many events were served reduced to their slot and how many were withheld, absent rather than zero when nothing was judged.
  • Storage quotas as shipped (ADR-151): two quotas per person, mail 50 GiB and Drive 1 TiB by default, none for a shared account, a system mailbox or an account declared unlimited, and no tenant quota any more. The check is the write; SMTP defers with 452 4.2.2, JMAP answers overQuota, IMAP NO [OVERQUOTA]; a server-wide disk guard ([storage] min_free_percent, 5 % by default) defers inbound mail and refuses additions before the disk fills (storage). The tenant upload route used by chat attachments and room avatars finally goes through the same admission.
  • A team’s mailbox activates from a client: Principal/enableMail lays down the six mailboxes, the sending identity, the task list, the root folder and the sorting script that used to require a terminal, answers which of four things actually happened (created, already live, an interrupted activation completed, or a half-laid component and nothing touched, which is a named error, never a success), serialises concurrent activations, and announces what it created on the team’s account so connected clients see the mailbox arrive. The directory says whether an address receives (Principal.receivesMail, distribution lists count as reachable), and a team that receives without being able to reply is a doctor failure that names the address and the repairing verb.
  • Writing a property the server does not know no longer answers “saved”: an unknown name, a wrong case, a renamed word used to be compared to nothing and acknowledged. The refusal now names the property, distinguishes an unknown name from a path into an absent property, and says when a property exists but is computed by the server or changed by another gesture. The hardening switches on type by type, the directory first (JMAP v2).
  • Folders and sorting rules, four fixes: a “new since your last visit” counter next to the unread total (folder_entered_at set on entry, FolderVisit/get and markViewed, Email/query filter newSinceVisit, advertised as folderVisit), on system and personal folders alike; two sibling folders can no longer carry the same name and a folder name carries no reserved character, guaranteed by a database constraint after repair; a rule can name its target by path (Archive/Crisp, advertised as ruleFolderPath) instead of by a bare name that two folders could carry (mail).
  • Search asks for every word, and an accent no longer decides: a multi-word query now requires all its words, with a fallback to any-word when that finds nothing, and résumé matches resume; Email/query can sort by relevance. sendAt and scheduledSendAt accept only the RFC 3339 string, closing a three-step wire migration. A message submitted by an authenticated account keeps that provenance with it; an import with no usable source date takes the message’s own Date: before the import time.
  • Chat: a room destroyed by its owner goes to a trash the owner can see and restore (room trash); who destroyed a room, removed a member, changed a role or moderated a message is now in the audit log, with the address and session it came from; the presence status gains a free-text note with an optional emoji, advertised in the chat capability with its two ceilings; the E2EE keySlots surface is retired (zero rows measured on six instances) and a message created with an encryption scheme other than none is refused instead of being stored as clear text.
  • Journals become readable together: oximail journal list reads the administration log and the data access log in one timeline with actor and object filters; AuditEvent/verify is capped at maxEventsPerVerify (2000) and resumes by nextAfter; a time bound on AuditEvent/verify and /query is re-stamped into the canonical form before it reaches SQL (it used to be compared as text, so 10:30:00Z excluded the very instant it named); the administration log’s retention is 365 days, aligned on the access log, and an erasure no longer touches the chain; extensions log through an ext: door; a tenant activating the access log chooses strict or best-effort for a line that cannot be written (compliance, CLI).
  • The client address behind a trusted proxy is no longer chosen by the client: Forwarded is read right to left, addresses are parsed or nothing, headers are never believed without a TCP peer, and a request whose address cannot be established is refused rather than served under "unknown". check-config refuses a loopback listener behind a proxy it does not recognise, since every visitor would otherwise share one rate-limit bucket.
  • Operability: /metrics has its own token ([admin] metrics_token_file), distinct from the administration token a Prometheus configuration had to hold; warnings and errors are counted per module; restore refuses a backup made by a newer binary and every verb that opens the database refuses one migrated by a newer binary; an unknown migration namespace is a boot refusal; oximail app-password revoke closes the sessions opened with it; a fuzzing harness covers the in-repo protocol parsers and found two Sieve lexer defects in its first minute; setup verify finally exits non-zero and says who renews the certificate; the wizard publishes the server’s IPv6 (AAAA, ip6:) and no longer mistakes a v6 address for a v4; no CLI verb requires a secret on its command line (operations).
  • Measured, then decided: SQLite now allocates in jemalloc, in an arena of its own (it carried 94 % of the heap in glibc); the calendar reminder scheduler read events under an unordered LIMIT 1000 and skipped 380 of 1 380 on the production box; the retention floor of 7 days on trash and chat is gone (the administrator’s value applies, from 1 day); the established-posture floor of the spam filter moves to 14 days on the strength of a measurement over 119 000 messages; brand impersonation no longer mistakes a logo for a link.
Early September
  • At rest is the volume, not the application (ADR-148, five lots in nine days): the SQLCipher database, the per-account blob keys and the self-describing blob header left the binary; the database is plain SQLite, blobs are their content named by its hash, and the at-rest protection is dm-crypt/LUKS under the instance, measured by the boot, by doctor at_rest and by the oximail_at_rest_volume gauge. The wizard refuses a bare volume unless --accept-bare-volume records the consent, and prints the six-point reference configuration. server.key, the tenant keys, escrow and rotation stay for the secrets they wrap; key purge-pending now removes every rotation leftover under one recovery drill; blob verify --walk checks the disk against the registry; restore refuses a sealed blob and names the binary of its era. The trigger was a measurement: locked key memory cost a factor of 6 to 20 on every request (encryption at rest).
  • The relay is not an archive (ADR-149): the body of a message submitted by a relay account is a file in a dedicated spool ([storage] relay_spool_path, its own mount point, excluded from backups), not a blob; it is unlinked in the same terminal write as its queue row at the destination’s 250 or on abandonment; the failure report carries nothing of the message and is never delivered to a local mailbox; a backup MX keeps mail in transit the same way; without a spool no relay submission is accepted, and doctor relay_spool measures the separation (outbound relay).
  • The data access log is served (ADR-150, lots 2 to 6): who read, listed, wrote, shared, exported or destroyed which object, in its own chained table with its own 365-day retention, under the accesslog capability, opt-in per tenant and fail-closed when active. Amendment 1 followed a refused benchmark (p99 +251 % on Email/get with one commit per call): the log traces third-party access, never an owner’s own reads. access-log list --actor answers “what did this collaborator do”, bounded by --since; the WAL checkpoint left the request path (compliance).
  • The tenant chain: an act of the node itself (pairing an organization, suspending it, rotating the working key, enrolling a root) had no home in the administration log; it now has its own chain with an origin record, audit-log list|verify|attest, a doctor check that also verifies every account chain, and a legitimate rewrite (erasure scrub, principal rekey) is an act of the chain rather than a break in it. The creator of a principal is recorded.
  • Meet, the shape as served: an external guest without an account joins by link (POST /meet/guest, a per-guest ephemeral TURN credential), the waiting room and the moderator role exist, an active participant can bring someone into a call already started, a group call can grow past the direct mesh through a media forwarder plugged in through the extension seam (no forwarder means today’s behaviour bit for bit), the server answers STUN itself on a port the operator opens, and a scheduled meeting carries its join link in the iTIP invitation, where the guest’s copy used to arrive without it (ADR-147, Meet). A stranger to a session gets the same answer as an invented id.
  • Notes, a new surface: the free capture of the workspace, for what is not a task (if it has a “done” state it is a task, otherwise a note). Notebook is the unit of sharing, with a three-bit rights vocabulary of its own; a Note carries a canonical ProseMirror body and a server-derived bodyText, both served only when named, free tags, links to other workspace objects that never break, a two-stage trash and [notes] trash_retention_days. Dictation is Note/set create with an audio attachment behind an engine seam: without an engine the page is accepted and answers transcription: "failed" with its reason, never silently. Note/transcribe adds a recording to an existing page or retries a failed one, and each audio part carries the provenance of its own transcription, provider, host contacted and a residency tier (local, ch, eu, non_eu) the client branches on, written on the attempt; the page claims no more than the least sovereign of its pieces (ADR-144, Notes).
  • Meeting invitations: the cancellation message has the form of the invitation, in the recipient’s language and at their clock; CalendarEvent/set adds a video link to a meeting that already exists instead of answering updated and doing nothing.
  • Shared mailboxes and calendars from every client: a shared mailbox appears in IMAP under Other Users/<address>/…, a read-only shared calendar no longer announces itself writable to CalDAV, a message can be copied from a shared mailbox to one’s own with its history, marking a message deleted in a shared mailbox requires the delete right, a mayWrite share no longer implies mayDelete on the Drive, and a request mixing one’s own account and a shared account is refused instead of answering from the wrong one. A frozen account’s grants are suspended while the operator’s stay. EventSource pushes the changes of shared accounts too.
  • Protocol conformance batch (measured against real mailboxes): IMAP answers BAD to a command out of its state, serves the THREAD, SEARCH MODSEQ, STATUS SIZE and QUOTA it advertises, honours QRESYNC on EXAMINE, accepts a literal in any command, refuses what it does not understand, and the ACL verbs are refused rather than acknowledged empty when the shared face is off; SMTP stamps Return-Path and a for clause, and MAIL refuses what is not a reverse-path; a CalDAV/CardDAV REPORT the server cannot name is refused instead of answering with the whole collection, a URL segment that is not an account is not a principal, and the DAV: header no longer claims class 2; WebSocket error frames have the RFC 8887 shape; SearchSnippet/get computes notFound whatever the filter; accountId is required on reads as on writes; a Sieve require of an unknown extension is refused per RFC 5228 (legacy protocols).
  • Isolation between accounts: seven gaps measured on the fleet in a conformance campaign, corrected the same week; a blob download carries ETag and Cache-Control since its id is its hash; a public share link can no longer be endless.
  • Operator surface: an unknown configuration key is an error and a repeated block nests (ADR-007 Am. 2); doctor finally loads the extension sections every other verb already read; extension binaries can add CLI verbs and doctor checks instead of intercepting them; the Drive trash has a per-tenant setting ([drive] trash_retention_days), a verb and one exit that respects legal holds, and the four other trashes (tasks, contacts, events, pages) go through it; file version retention is a per-tenant setting applied hot; oximail drive share grants one folder from the CLI and rights audit sees files; the rollback of a deployment is proven before the swap by the target binary; the two hundred admin commands open one connection instead of a pool (four in ten invocations used to crash on five boxes out of six); backup and blob verification no longer evict the page cache; deleting a batch of messages frees its attachments in seconds rather than days; accented mail reaches a receiver without 8BITMIME by downgrade instead of being lost without a bounce; an address resolves through one path.
  • Files, at the doors: a file held open by a protocol that has an “open” (an SMB engine plugged into the same binary) is refused to JMAP writers with forbidden, naming the holder and the age of the lock, and FileNode/get serves lockedBy; the registry is in memory, in the process, so a hold dies with the connection; a file created without contentType gets the type its name implies, an explicit one always wins (files).
  • Sieve says what it implements: the capability object was {}; it now carries the implementation name, the script and name size caps and the extension list from the constants the compiler applies, null where nothing is capped; a require of an absent extension is refused at compile time naming what is available, and the three comparators may be required; onSuccessActivateScript is honoured in the transaction of the writes it follows instead of being ignored while the answer said “created”; activating a script that does not exist changes nothing, where it used to deactivate every other script first (Sieve).
  • Licence and doctrine: the server will be published fair source under the OxiMail License, whose every release becomes AGPL four years after publication (ADR-142, superseding ADR-004); the browser talks only to its own origin and every third-party call goes through the server (ADR-143); a spam verdict carries its axis so a forwarding tolerance can re-dispose per recipient without lying to the scorer (ADR-146); the E2EE vestiges of ADR-020 left the schema.
Late August
  • Meet grows up: invitation links (one per meeting, redeemable N times), the waiting room and the moderator acting on others, a maxParticipants that is enforced, a session end that no longer rewrites history, an invited guest who writes in the call’s chat, meeting rows that are finally retained and erased, Preferences accepting the meet section two client features already wrote to, and the join link fabricated with the right host in outbound invitations (Meet).
  • Time, done once: the core carried two recurrence engines, one time-zone aware and one not, and a weekly 9:00 series crossed a DST change one hour off; there is one now. One way to read a wall-clock hour, with the DST gap readable in the zone. A work schedule can say “22:00 to 06:00 the next day” (endDayOffset, ADR-139), the client asks the server first whether it understands the notion, and a day’s availability is computed from the previous day as much as from itself. A person’s time zone can be removed, was written under a name the server did not read, and an organization’s default zone is correctable from a client; doctor computes the next DST switch per zone in use and counts what falls in the hour that does not exist; the “calendar reminders” checkbox now cuts something; an availability incident leaves something to look at. A three-day seminar is no longer a free calendar.
  • Extension seams (ADR-137, ADR-138): system accounts with no mail surface, an administrative calendar share verb, collections resolvable on the shared: prefix, and an optional external contributor to a principal’s free-busy whose failure is serverFail rather than “free”.
  • Anti-spam, the mechanisms named by a J+7 measurement: a rule forward no longer launders detected spam; the model’s own verdict no longer smothers the other signals, and an established correspondent no longer goes to Junk on the statistical filter’s opinion alone; a Bcc-blast outside the TLD list reaches the suspected band; the webmail’s junk button finally trains the filter in both directions; a verdict lost at delivery is no longer swallowed; the list-unsubscribe signature is no longer a free pass above the threshold; DNSBL answers are read, not believed; the sender relation is read both ways; a gesture that contradicts the filter is an opinion, not a lesson; a transaction status is not a sender name; a bank impersonation with nothing of the bank’s, four link deceptions and the links hidden in encoded HTML are seen; a fake delivery notice in German no longer passes; phishing is no longer filed with advertising; mail forwarded by your old provider is no longer condemned by construction; the DMARC report alert says what the reporter explained (anti-spam).
  • Directory and teams: a team can own its address (reserved on creation, no longer silently backed by its creator’s account); colleagues carry their name in the address book; the profile fields live on the directory card only, seeded from existing profiles; a distribution group can no longer eclipse an existing address; a leaver’s departure is one command; disabling an account finally closes the door, including an open IMAP session, and disabling the last administrator is no longer a one-way door; the legal hold has its first step.
  • Mail and search: every message knows its language, an old one at open time without a backfill, and language is a search criterion; “re: facture” searches for real (a colon is no longer a field name); a muted conversation stays muted over time; the quota surface is a list of real figures rather than one object with a fictitious ceiling; a refused quota says which and by how much; the delivery receipt works with a cancellable send; an encoded display name with commas no longer explodes into empty senders; an unsupported filter and a bad filter value are two different errors; the announced upload concurrency is enforced; a filtered /changes no longer names messages that never concerned it; the streaming push no longer loses its window; @here, @channel and @all finally notify someone; a reaction cannot weigh a megabyte; custom emojis are bounded and can be switched off per organization; stickers have their calibre.
  • Drive and files: the dates of migrated documents survive; the owner of a shared space no longer loses control of it and learns when they overrode a share; the first folder of a shared space; finding a file in a large folder costs the same as in a small one; a 27 GB file no longer goes through memory (ADR-145); an address book re-imported without ids no longer duplicates; “last exchange” looks backwards; the address book answers the questions that make it useful, and its columns and saved views live on the account.
  • What was retired or told the truth: six settings that announced a guarantee nothing consulted, a green check that proved nothing, three published feedback loops that were dead, a service that announced a sandbox no machine applied, a migration client that accepted any certificate, a hardened service that could not read its own key, two ports that would have opened unasked, a diagnostic that reported an unrun check as passed, DNS names and HTTPS names that could diverge, a certificate that ignored a new name, public pages without their browser protections, an operations journal drowned in 144 000 warnings, and a log that recorded how fast a client read its mail. The root key in clear on a bare volume is named, and its purge is reachable again. Deleted mail leaves the search index files, not just the index.
  • CLI: the admin commands start twice as fast; a new export writes one file per message (nine seconds where it took two hours for five thousand); the command taxonomy is decided and guarded (blob as the single root); an admin verb never follows the delivery wildcard again (a set-password on a non-existent address had reset the catch-all owner’s password); an administration command no longer mints a root key by mistake; the backup MX starts through the same path as the others; the wizard’s DNS and HTTPS name lists come from one source.
  • Observation, bounded (ADR-140): the log level is raised for a bounded time only, oximail log elevate --directives … --duration-secs N or PUT /admin/v1/logs/level, 60 to 3600 seconds, and it reverts on its own, the elevation and the return both journal events (debug had been on since April on the oldest production box, never turned off); the protocol trace projector captures the raw exchange of one account or one peer, never everyone, for 60 to 900 seconds, redacted, 0600 on the tmpfs, purged on stop; log tail renders the journal one readable line per event (operations, admin API).
  • Told by name, not swallowed: a contact created with properties the server does not know answered success and existed without a name, it is refused naming them; Rule/test, Rule/apply and the CLI dry-run read the stored spam verdict instead of “not scored”; a task carries its timeZone so a DUE:…Z from CalDAV no longer floats and a reminder fires at the task’s instant; the real-time stream kept losing a pending window when a client resubscribed, the subscription now replaces the list of followed types and only that; an IMAP import keys its resume on the source’s UIDVALIDITY, so a renumbering neither skips nor duplicates, and a source that announces none fails rather than resuming on ambiguous keys (contacts, tasks, migration).
Mid-August
  • Meet, the first working shape: real-time calls gain a server surface — a session anchored on a chat room, whose creation reaches every member of that room so a callee learns of the call without a dedicated notification type, plus a transient relay for the WebRTC handshake that is never persisted and never logged. Phase 1 is peer-to-peer with up to three active participants, and membership is a self-expiring heartbeat lease rather than a flag (Meet).
  • Email/get gains its tree: bodyStructure is emitted and recursive, fetchAllBodyValues finally reaches every text/* leaf — the text/calendar part of an invitation included — and attachments carry partId, charset and disposition. Per-part headers are served under bodyProperties, the only RFC route to the Content-Type parameters that tell an invitation from an attached .ics. Contract change: with no fetch*BodyValues argument, bodyValues now comes back empty as RFC 8621 §4.2 requires, where it used to be populated unconditionally (the body tree).
  • Every /get closes its vocabulary, and the canon is published: an unknown property name is invalidArguments naming the offenders rather than a silently ignored word, and which properties are selectable — and which are computed per caller — is now emitted in machine form next to the generated types (the projection canon).
  • Shared mailboxes read and write correctly: the decryption key follows the effective account rather than the caller, so bodies in a shared mailbox open; and a message filed by a grantee is encrypted for the owner, closing the mirror of the same defect. The key layer itself left with ADR-148 three weeks later; the effective-account rule survives it as the scope of the blob registry.
  • Thread/get and Thread/changes return to pure RFC 8621 clients, served under the mail capability where §3 puts them; a client declaring only core + mail used to be told unknownCapability on a mandatory method (mail).
  • Tenant contact directories: an organization can publish shared contact sets that reach members as read-only cards in their own address book, with entries maintained by named principals and groups (contacts, ADR-133).
  • Post-quantum key exchange is actually negotiated: X25519MLKEM768 is offered first, with classic X25519 as fallback, on 25, 465, 587, 993 and 443. The feature had been compiled in for months while the installed provider could not implement it, so every session silently fell back (TLS & ACME).
  • Two years of upstream encryption: SQLCipher 4.5.7 to 4.14.0, SQLite 3.45.3 to 3.51.3, and the shipped binary checked at deploy time to depend on no system crypto, TLS or SQLite library. Three weeks later ADR-148 removed SQLCipher from the binary altogether (early September, above); the dependency check survives it.
  • Handshake logs stop crying wolf: the four TLS listeners separate a peer that hung up before negotiating from a real handshake failure, each branch carrying a stable reason field. One production instance was emitting 480 harmless warnings a day from a single monitoring probe — and a real failure would have hidden inside them (operations).
  • dkim=fail says why: the failure reason reaches the Authentication-Results header and the stored per-message result, so a post-mortem can separate an invalid signature from a body altered in transit, a missing key, or a signature dated in the future — which means the server’s own clock is behind (email authentication).
  • Operability: oximail sql routes by the shape of the statement, so a WITH … SELECT returns rows instead of reporting “0 rows affected”; blobs gc will not collect an unreferenced blob younger than a 24-hour upload grace window (RFC 8620 §6.1); local rollback snapshots gained a retention policy the deploy path applies; doctor judges clock health; and limits that were only announced became limits something enforces (CLI, operations).
  • Time zones: a tenant’s default time zone is readable, inherited and monitored, and a time zone supplied at provisioning now reaches the principal — an invalid one is refused before the first write instead of being accepted and ignored.
Late July and early August
  • Federation, first working shape: the bilateral out-of-band pairing ceremony (fingerprint over realm and key, node key per organization), a portable identity root distinct from the disposable working key on the box (rotation is revocation, by monotonic epoch), and the cross-organization grant model — realm-scoped, owner-written, self-expiring, widening the access axis only. Pairing revocation suspends access rather than destroying the record; erasure notices are stamped on the identity anchor and fail closed at delivery (ADR-124 to ADR-127).
  • Read receipts, both surfaces. Mail gains the RFC 9007 MDN surface — MDN/send composes a receipt as an explicit user gesture, MDN/parse finally makes the inbound receipts already linked to your sent mail readable. Chat read receipts now reach every member of a group or channel live instead of only a direct peer, with a per-channel visibility switch and a room-size ceiling on the live fan-out (chat).
  • Admin API: an account’s trusted-sender list, the set that outbound sends and ham rescues also write, is readable and editable (GET/POST/DELETE /admin/v1/accounts/{id}/trusted-senders), since a standing rule nobody can list is invisible; an external directory connector pushes organization contacts into the accounts it names (POST /admin/v1/contacts/org-sync) (admin API).
  • Drive version history on the wire: FileVersion/query, get and restore, with maxVersionsPerNode advertised and pruning driven by the same constant (files).
  • Sync honesty: a Foo/changes cursor older than the 90-day replay window is now refused with cannotCalculateChanges instead of being told it is synced — one store-level chokepoint every consumer inherits, DAV sync-collection included. /changes also collapses to one outcome per id per RFC 8620 §5.2, and an empty /set batch no longer skips its ifInState check (JMAP Core).
  • Streaming credentials: SSE streams open with a 60-second ephemeral ticket instead of the session token, so a 24-hour credential no longer travels in a URL that proxies log.
  • Wire shapes corrected where they leaked storage: chat link previews are typed objects rather than an array of raw byte values; a message/rfc822 part is advertised as the attachment it is, instead of publishing the encapsulated message’s own attachments at the top level; four calendar storage internals left the wire.
  • IMAP: FETCH RFC822, RFC822.HEADER and RFC822.TEXT returned no payload — and RFC822 marked the message \Seen anyway. Both fixed (legacy protocols).
  • Retention and backup integrity: per-organization backup no longer silently omits attachment blobs or the rows of tables missing from a hand-maintained list, and verify-backup can see the difference; the trash that expiring messages sweep into finally has a retention policy behind it; body-part blobs became the derived cache ADR-098 always described, re-derived and hash-verified on a download miss (operations).
  • Operability: eleven boot-time janitors no longer collide into sqlite busy and a lost tick on every start; doctor stopped reporting the whole delivery queue as pending; a read-only spam check verb asks the filter its current opinion of a stored message; inbound DSN and ARF now raise bounce and complaint events on the ingest path.
  • App-password scopes are enforced end to end: the scope rides the token and survives refresh, one capability matrix decides, and protocol access derives from the capability set — enforced at JMAP dispatch, IMAP login and DAV (ADR-129).
  • Anti-spam: RCPT-time greylisting was retired as a defence — botnets retry, sender IP pools break the triple, and it taxed legitimate cold senders. Authentication enforcement and the scored pipeline carry that load (ADR-128, anti-spam).
  • Setup: the wizard gained a smarthost profile as a third entry, recorded as an audited intent marker rather than a role (first boot).
  • Envelope-sender domains: a platform sending through the MSA can use a technical MAIL FROM on a DNS-proven domain — VERP forms included — while the From: header stays the human identity. The granted domain must share its organizational domain with an identity of the account, so SPF alignment under relaxed DMARC holds by construction (bulk sending).
Early July
  • Compliance: per-organization mail journaling (inbound/outbound, mailbox or external archiver), and account erasure rebuilt as a durable, resumable phase machine with a reversal buffer, DSAR proof (oximail erasure prove), and audit-log retention enforcement.
  • Performance: a measured load-test campaign roughly tripled read throughput (per-connection prepared-statement cache) and doubled saturated ingest (composite thread index); single-message ingest lost its fixed latency floor (search commits on quiescence); the WAL is bounded and observable.
  • Delivery correctness: ingest delta-sync visibility became atomic with the message row; recipient Rules and vacation now run on local-to-local delivery (ADR-117); outbound hops mediate ESMTP extensions (SMTPUTF8/8BITMIME/SIZE) instead of silently bridging; DKIM oversigns From/To/Subject.
  • Client interop wave (verified against Apple Calendar, Thunderbird, eM Client, Outlook invitations): DAV bootstrap at the server root, CDATA-safe XML, content-hash ETags, real calendar-query filters, VTIMEZONE emission, VALARM modelling, EXPUNGE-correct IMAP announcements, and an eM Client preconfigured settings endpoint.
  • Configuration overrides are listable: GET /admin/v1/config returns the overrides in force and knownKeys, the closed set of keys the code consumes, so a typo cannot become a silent setting (admin API).
  • Auth: DPoP v2 — server nonce, per-request resource proofs, and the single-blob media token.
May – June
  • Outbound smarthost relay (ADR-116): relay accounts with DNS-proven domains, one-time enrolment, recipient-per-day quotas with warm-up, ephemeral relay retention, DNSBL self-monitoring (outbound relay).
  • Security batch: constant-work app-password verification, SSRF hardening on push and unfurl fetchers, fail-closed shared-account dispatch, submission From: header authorization, token at-rest hashing.
  • Spam pipeline overhaul: Bayes corpus epochs and rebuild, authenticated-only trusted-reply whitelist, disposition spine (anti-spam).
  • Drive and collaboration: rooted drive with per-viewer stars and server thumbnails, share-link accounting, conversation timeline, chat capacity caps and member-scoped typing.
  • Wire conformance sweep: the /query silent-ignore class eliminated (unknown filters/sorts/arguments are errors), extension capabilities advertised truthfully in accountCapabilities.
April — v0.30.0 The pre-launch hardening release: the fail-loud discipline swept across every surface, tenant isolation at the storage layer, encryption at rest by default, the two-path SMTP model, the legacy bridges, and the setup wizard as shipped today. For what is real versus specified, every page of this documentation states the version it describes — the site documents the v0.30.0 line as deployed, never planned work presented as shipped.