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 pairtakes 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._tcprecord, 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/proposeTimesasks 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 inunreachableEmails, 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 mostmirror_staleness_secsold (120 s), refuses every write withmirror-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 retireerases its escrow. An identity whose chain no longer verifies is re-certified with an enrolled sister’s paper key (oximail federation working-key repair), andoximail federation roots deleteabandons one on purpose, after which every peer pairs again. A tenant’s federation jurisdiction is its declared country of establishment:oximail federation identityreads it out beside the fingerprints, a pairing records what was declared to each peer, anddoctorsays 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.
doctornames it,oximail_search_index_statepublishes it, andEmail/querydeclares an answer given without it (fullTextIndexUnavailable). After an upgrade the index rebuilds itself on its own task, served in full meanwhile, andoximail reindexrebuilds 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:bodylooks in the body alone,textin 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 IMAPSEARCH BODYandSEARCH TEXTgo 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 (reunionmarksRé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
Receivedline 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, anddoctorno 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 suspendlifts this for a bounded time). The resolver configuration no longer denies a record just published for half an hour (oximail resolver ensure --applyrewrites an older one), and the wizard empties the local cache after publishing.oximail setup dnsrun again reads the zone first and never doubles a record (a second_mta-stsTXT 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.doctorchecks 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 listshows 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 answersstepUpRequired, 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.lastMessageCalllets 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/getandChatRoomDirectory/query, announced byroomDirectoryin the chat capability), with their name, description and member count only.ChatMessage/queryreads 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.2or* 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.2to 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 saysnullwhen 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.txtand 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.
- Backups for a box without a hypervisor:
oximail backup --towrites an age-encrypted, incremental repository the box can write but never read, restorable with theagetool alone. Into ans3://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 thatdoctorreads.oximail account import --from-backupbrings one account back beside what it holds, notes included, andoximail chat restore-room --from-backuptakes 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 replayerases 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/testtests an unsaved rule or the whole set,Rule/applynames 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 withForwardConfirmation/set, the holder receives a code valid seven days, andForwardConfirmation/confirmopens 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,legalIdentityonTenant/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, anddoctor legal_identitynames 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/getis a valid update patch.Email/getserves headers in message order andheader:<Name>in all seven RFC 8621 forms, andtextBodyandhtmlBodyfollow §4.1.4.EmailSubmission/setsends a draft a standard client created and answers typed refusals;Thread/changesreports 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;alreadyExistsnames the existing record (existingId); a method outsideusingisunknownMethod. 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 answeringcannotCalculateChangesfor good (JMAP core, mail). - IMAP, CalDAV and CardDAV conformance (measured with imaptest and real clients):
\Draftis a permanent flag, IMAP4rev1 clients see\Recent,LISTfollows LIST-EXTENDED andCREATEno longer subscribes.SORTfollows RFC 5256 and RFC 5957,THREADno longer keeps a parent that a message’s ownReferencescontradict,APPENDkeeps its date, another session’s flag changes and expunges reachNOOP, andENVELOPEwrites 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:
DATAends only at<CRLF>.<CRLF>, so one transaction carries one message; the inbound door answers as RFC 5321 says, andpostmaster@andabuse@reach the tenant on every one of its domains. A forgedFrom:no longer passes DMARC and a message without a single author is refused;c=simplesignatures verify, a domain with exactly ten SPF terms passes, and a signature whosel=leaves part of the body unsigned isdkim=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 (doctorcounts 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 aFeedback-ID. A sending domain’s proof belongs to the tenant’s own domain, and outgoing mail leaves over IPv4 whenever the destination has one. A250after 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 IMAPAPPENDare traced, never refused. Legacy Office documents, Outlook messages andwinmail.datare read, and a Drive file whose content is an executable is served asapplication/octet-stream. A new box observes and counts before any rule is armed (attachment policy). - Mail, notifications and grants: an encrypted message carries the
$encryptedkeyword (oximail email backfill-encryptedmarks 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-previewre-derives the stored ones), andSenderBrand/getgives 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).
- Received S/MIME signatures are verified (ADR-156): RFC 9219 as written, under
urn:ietf:params:jmap:smimeverify, withsmimeStatus,smimeStatusAtDelivery,smimeErrors,smimeVerifiedAtand 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 reachesEmail/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 issmimeSignerunderv2:mail. The server never decrypts: an encrypted message is verified by the client. Mail stored before the feature is marked byoximail smime backfill(signed messages, operator side). - OpenPGP keys found by the server:
RecipientKey/lookuptries the recipient domain’s WKD, then the key server, and tells “not found” from “unreachable”;PgpKey/publishpublishes one’s own key. Advertised underrecipientkeysand 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 1pin no longer moves every 60 to 90 days.oximail dane rotatechanges 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 bydoctorand 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.1instead of authorizing forever or bouncing, anddoctor sender_domain_proofswatches it. A submission withoutDate:orMessage-ID:gets one from the server, and a secondFrom:is refused and never signed. An internationalizedFrom: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 originalMessage-IDagain, 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
doctorround every 15 minutes, andHealthReport/queryandHealthReport/getserve 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 --jsongives 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 answeringaccountReadOnly. Amailapp password can send through JMAP as it already did through SMTP.GET /healthcounts 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 scheduledoximail backup), runs the recovery-key ceremony (shown once, typed back, the drill recorded;oximail key escrow --guidedon an existing box), creates the/metricstoken, 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,statusandpurgechange 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-secretsmoves them). Each organization has its own overrides through/admin/v1/tenants/{id}/config, andoximail config set-overriderefuses 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
brandMarkTypetelling a registered mark (VMC) from a common-law one (CMC). A DKIM-signedBIMI-Selectoris honoured, andoximail bimi backfillgives the logo to mail already in the box (configuration). - Chat: the body of a message or room is
content;encryptedContentis 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/setacceptsbodyStructureon 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-atrepairs 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.
DTENDbecomes the duration on every door and comes back unchanged over CalDAV. Availability says when it could not see everything (complete,incompleteForonEvent/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.
- A shared calendar no longer leaks what confidentiality hides (ADR-152): sharing already reduced what a colleague receives of a
privateevent and served nothing of asecretone, but search still looked at the real content, so a title filter answered yes/no and a time bound revealed thesecretslots. “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: asecretappears in no result set, nototaland no page; aprivatestays 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 answersoverQuota, IMAPNO [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/enableMaillays 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 adoctorfailure 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_atset on entry,FolderVisit/getandmarkViewed,Email/queryfilternewSinceVisit, advertised asfolderVisit), 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 asruleFolderPath) 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ématchesresume;Email/querycan sort by relevance.sendAtandscheduledSendAtaccept 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 ownDate: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
keySlotssurface is retired (zero rows measured on six instances) and a message created with an encryption scheme other thannoneis refused instead of being stored as clear text. - Journals become readable together:
oximail journal listreads the administration log and the data access log in one timeline with actor and object filters;AuditEvent/verifyis capped atmaxEventsPerVerify(2000) and resumes bynextAfter; a time bound onAuditEvent/verifyand/queryis re-stamped into the canonical form before it reaches SQL (it used to be compared as text, so10:30:00Zexcluded 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 anext: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:
Forwardedis 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-configrefuses a loopback listener behind a proxy it does not recognise, since every visitor would otherwise share one rate-limit bucket. - Operability:
/metricshas 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;restorerefuses 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 revokecloses 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 verifyfinally 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 1000and 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.
- 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_restand by theoximail_at_rest_volumegauge. The wizard refuses a bare volume unless--accept-bare-volumerecords 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-pendingnow removes every rotation leftover under one recovery drill;blob verify --walkchecks the disk against the registry;restorerefuses 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’s250or 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, anddoctor relay_spoolmeasures 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
accesslogcapability, opt-in per tenant and fail-closed when active. Amendment 1 followed a refused benchmark (p99 +251 % onEmail/getwith one commit per call): the log traces third-party access, never an owner’s own reads.access-log list --actoranswers “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, adoctorcheck 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).
Notebookis the unit of sharing, with a three-bit rights vocabulary of its own; aNotecarries a canonical ProseMirrorbodyand a server-derivedbodyText, 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 isNote/set createwith an audio attachment behind an engine seam: without an engine the page is accepted and answerstranscription: "failed"with its reason, never silently.Note/transcribeadds 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/setadds a video link to a meeting that already exists instead of answeringupdatedand 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, amayWriteshare no longer impliesmayDeleteon 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.EventSourcepushes the changes of shared accounts too. - Protocol conformance batch (measured against real mailboxes): IMAP answers
BADto a command out of its state, serves theTHREAD,SEARCH MODSEQ,STATUS SIZEandQUOTAit advertises, honours QRESYNC onEXAMINE, 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 stampsReturn-Pathand aforclause, andMAILrefuses what is not a reverse-path; a CalDAV/CardDAVREPORTthe 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 theDAV:header no longer claims class 2; WebSocket error frames have the RFC 8887 shape;SearchSnippet/getcomputesnotFoundwhatever the filter;accountIdis required on reads as on writes; a Sieverequireof 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
ETagandCache-Controlsince 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);
doctorfinally loads the extension sections every other verb already read; extension binaries can add CLI verbs anddoctorchecks 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 sharegrants one folder from the CLI andrights auditsees 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, andFileNode/getserveslockedBy; the registry is in memory, in the process, so a hold dies with the connection; a file created withoutcontentTypegets 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,nullwhere nothing is capped; arequireof an absent extension is refused at compile time naming what is available, and the three comparators may be required;onSuccessActivateScriptis 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.
- Meet grows up: invitation links (one per meeting, redeemable N times), the waiting room and the moderator acting on others, a
maxParticipantsthat 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,Preferencesaccepting themeetsection 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;doctorcomputes 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 shareverb, collections resolvable on theshared:prefix, and an optional external contributor to a principal’s free-busy whose failure isserverFailrather 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
/changesno longer names messages that never concerned it; the streaming push no longer loses its window;@here,@channeland@allfinally 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 (
blobas the single root); an admin verb never follows the delivery wildcard again (aset-passwordon 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 NorPUT /admin/v1/logs/level, 60 to 3600 seconds, and it reverts on its own, the elevation and the return both journal events (debughad 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,0600on the tmpfs, purged on stop;log tailrenders 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/applyand the CLI dry-run read the stored spam verdict instead of “not scored”; a task carries itstimeZoneso aDUE:…Zfrom 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’sUIDVALIDITY, so a renumbering neither skips nor duplicates, and a source that announces none fails rather than resuming on ambiguous keys (contacts, tasks, migration).
- 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/getgains its tree:bodyStructureis emitted and recursive,fetchAllBodyValuesfinally reaches everytext/*leaf — thetext/calendarpart of an invitation included — and attachments carrypartId,charsetanddisposition. Per-part headers are served underbodyProperties, the only RFC route to the Content-Type parameters that tell an invitation from an attached.ics. Contract change: with nofetch*BodyValuesargument,bodyValuesnow comes back empty as RFC 8621 §4.2 requires, where it used to be populated unconditionally (the body tree).- Every
/getcloses its vocabulary, and the canon is published: an unknown property name isinvalidArgumentsnaming 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/getandThread/changesreturn to pure RFC 8621 clients, served under themailcapability where §3 puts them; a client declaring onlycore+mailused to be toldunknownCapabilityon 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
reasonfield. 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=failsays why: the failure reason reaches theAuthentication-Resultsheader 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 sqlroutes by the shape of the statement, so aWITH … SELECTreturns rows instead of reporting “0 rows affected”;blobs gcwill 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;doctorjudges 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.
- 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/sendcomposes a receipt as an explicit user gesture,MDN/parsefinally 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,getandrestore, withmaxVersionsPerNodeadvertised and pruning driven by the same constant (files). - Sync honesty: a
Foo/changescursor older than the 90-day replay window is now refused withcannotCalculateChangesinstead of being told it is synced — one store-level chokepoint every consumer inherits, DAVsync-collectionincluded./changesalso collapses to one outcome per id per RFC 8620 §5.2, and an empty/setbatch no longer skips itsifInStatecheck (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/rfc822part 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.HEADERandRFC822.TEXTreturned no payload — andRFC822marked the message\Seenanyway. 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-backupcan 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 busyand a lost tick on every start;doctorstopped reporting the whole delivery queue as pending; a read-onlyspam checkverb 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 FROMon a DNS-proven domain — VERP forms included — while theFrom: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).
- 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-queryfilters, VTIMEZONE emission, VALARM modelling,EXPUNGE-correct IMAP announcements, and an eM Client preconfigured settings endpoint. - Configuration overrides are listable:
GET /admin/v1/configreturns the overrides in force andknownKeys, 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.
- 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
/querysilent-ignore class eliminated (unknown filters/sorts/arguments are errors), extension capabilities advertised truthfully inaccountCapabilities.