v0.16.19

TanStack/queryv0.16.19Aug 24, 2026by github-actions[bot]

Upgrading?

AI Summary

This update focuses on improving security and data integrity, adding OAuth client configuration for the WebUI, and exposing spam filter results to Sieve scripts, while addressing numerous bugs in CalDAV, Directory, and MTA handling.

Key Highlights

  • WebUI OAuth client configuration (`oauthClientId` setting)
  • Sieve script integration exposing spam filter results (`env.spam.score`)
  • CalDAV fixes for attendee address parsing and MKCALENDAR/MKCOL permissions
  • MTA improvements regarding catch-all addresses and DMARC report handling
  • MySQL/MariaDB key column schema changes requiring manual migration

Breaking Changes

  • MySQL & MariaDB: Key columns changed from TINYBLOB to VARBINARY(255) with full-length primary keys, requiring manual ALTER TABLE statements for specific tables.

New Features

  • OAuth client configuration for WebUI
  • Sieve environment variables for spam filtering
  • CalDAV default vCard version setting
  • ACME order and authorization failure handling
  • Redis Sentinel deployments using TLS for master connections

Full Release Notes

## [0.16.19] - 2026-08-24

If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If you are upgrading from v0.15.x and below, please read the [upgrading documentation](https://github.com/stalwartlabs/stalwart/blob/main/UPGRADING/v0_16.md) for more information on how to upgrade from previous versions.

## Added
- WebUI: `oauthClientId` setting in `Application`, which allows the WebUI to use a different OAuth client than the default.
- Sieve: `env.spam.score` and `env.spam.is_spam` variables, which expose the spam filter result to system scripts running at the `DATA` stage.
- CalDAV: `vCardVersion` setting in `AddressBook`, which allows the default vCard version to be specified when the client does not request a specific version.

## Changed
- MySQL & MariaDB: Key columns are now `VARBINARY(255)` with a full-length primary key instead of `TINYBLOB`. Note: Existing deployments should run, once per table, for each of the tables `a`, `d`, `e`, `f`, `g`, `h`, `j`, `k`, `l`, `m`, `n`, `o`, `p`, `q`, `r`, `s`, `t`, `u`, `w`, `x` and `y` the command `ALTER TABLE a MODIFY k VARBINARY(255) NOT NULL;`.

## Fixed
- ACME:
  - Order and authorization failures are never logged, so an order rejected by the CA.
  - An order rejected by the CA marks the renewal task as permanently failed.
- CalDAV:
  - Attendee addresses whose `mailto:` URI percent-encodes a full `name-addr` are silently dropped from the scheduling snapshot.
  - Attendees whose calendar user address cannot be parsed should be flagged with `SCHEDULE-STATUS=3.7`.
  - The RSVP link in an iMIP invitation stamps `PARTSTAT` on the organizer's copy of the event only, leaving a local attendee's own copy at `NEEDS-ACTION` and sending the organizer no reply.
  - `MKCALENDAR`, `MKCOL` and `PROPPATCH` store the display name, description, time zone and the other per-user properties under the authenticated account rather than the account that owns the collection.
- Directory: 
  - An empty column, attribute or claim returned by an external directory is synchronized as an empty string rather than a missing value.
  - `/api/discover` splits the account name on `@` without accounting for the `%` master user separator or the recovery administrator.
- FoundationDB: Older chunked entries are not deleted.
- IMAP: `SETACL` and `DELETEACL` fail to resolve an identifier spelled with uppercase characters.
- iMIP: Invitations, replies and cancellations reference a `TZID` parameter with no matching `VTIMEZONE` component whenever the event was stored without one.
- JMAP:
  - `AddressBook/get`: A new account's default address book is never recorded.
  - `Email/get` and `Email/parse` with `fetchAllBodyValues` return body values only for the parts listed in `textBody` or `htmlBody`, omitting every other `text/*` part in `bodyStructure`.
  - `Email/set` writes display names as an RFC 2047 encoded-word wrapped in a quoted-string, which RFC 2047 forbids.
  - `Mailbox/set`, `AddressBook/set` and `Calendar/set` store `isSubscribed` and the other per-user properties under the authenticated account rather than the account named in the request.
  - `Principal/query` returns no results when the `name` or `email` filter is spelled with uppercase characters.
  - `FileNode/set`: File nodes created over JMAP are returned with a `<D:href>` holding the raw name over WebDAV.
- Meilisearch:
  - Queries return at most 1000 results, as the `maxTotalHits` pagination setting is left at the Meilisearch default.
  - Searches combining several terms return documents that match only some of them.
  - A task confirmation timeout is reported as a success when `failOnTimeout` is disabled.
- Import: `--import` always aborts with the target database already containing data in the key range being imported.
- MTA:
  - A domain `catchAllAddress` pointing to a mailing list or a sub-addressed mailbox is accepted at `RCPT TO` and then rejected at local delivery with `550 5.5.0 Mailbox not found`.
  - `is_local_address()` and `is_local_domain()` expression functions do not match an address or domain spelled with uppercase characters.
  - Relay routes are rejected with `host resolves loopback address`, which prevents relaying through a local proxy or tunnel.
- MySQL, MariaDB & PostgreSQL: Range scans, range deletions and store purges run as a single unbounded statement, so on servers that enforce a statement timeout they abort on large accounts and tasks such as account deletion can never complete.
- Network: `local_port` and `local_ip` report the address Stalwart is bound to rather than the address the client connected to when the connection arrives through a trusted proxy.
- Task manager:
  - `totalDeadline` is not enforced on tasks that fail with a specific retry time.
  - Indexing tasks are dropped after `maxAttempts` failures, so a search store that is unavailable or overloaded leaves messages permanently missing from the index.
  - Indexing tasks are dropped when the document metadata read returns no data, which can happen on SQL read replicas that have not yet caught up with the primary.
  - The DNS management task republishes the DKIM records of retired keys that the DKIM rotation task had already removed from the zone.
- Sieve: `spamtest` returns only `1` or `10` (and `spamtest :percent` only `0` or `100`), so scripts cannot act on intermediate spam scores.
- Spam filter: `MIME_BAD` is tagged whenever the declared `Content-Type` of an attachment is not byte identical to the type detected from its magic bytes.


<hr />

### Check binary attestation [here](https://github.com/stalwartlabs/stalwart/attestations/42683556)

Related Repositories