How can I delete a broken type?

Your question

I am trying to delete the MtaOutboundThrottle configuration since it broke during an upgrade. But I am unable to, since JMAP just reports an error.

imanuel in :globe_with_meridians: Stalwart 01 Server in ~ RAM 232MiB/1GiB CPU 0.45
❯ stalwart-cli --user=“admin” --url=http://127.0.0.1:9090 query MtaOutboundThrottle
Password:
error: jmap error: serverUnavailable: This server is temporarily unavailable. Attempting this same operation later may succeed.

I am running stalwart on Ubuntu 26.04 with a postgres database.

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Recovery mode is the documented way to repair a configuration that stops the server from working normally. I couldn’t find documentation on why query MtaOutboundThrottle returns serverUnavailable. I’m assuming the broken object is the cause, but I haven’t confirmed that.

Steps (v1.0):

  1. Add STALWART_RECOVERY_MODE=1 and STALWART_RECOVERY_ADMIN=admin:<password> to the service environment. On script installs that is /etc/stalwart/stalwart.env.
  2. Restart Stalwart. In recovery mode the mail listeners don’t start. Only a plain HTTP management listener opens, on port 8080 by default. Keep that port unreachable from the internet, because brute-force protection is off in this mode.
  3. Point the CLI at that port, then find and delete the object:
stalwart-cli --user admin --url http://127.0.0.1:8080 query MtaOutboundThrottle
stalwart-cli --user admin --url http://127.0.0.1:8080 delete MtaOutboundThrottle --ids <id1,id2>
  1. Remove both variables from the environment and restart normally.

If the query still fails in recovery mode, check the server’s log output for the actual error. Recovery mode logs to the console. Please post that output here, because the failure then comes from something other than the normal service state.

If you are upgrading from an earlier release, check that your version matches these docs. See the upgrade guide: Overview | Stalwart

Docs:

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

This is the server logs:

imanuel in :globe_with_meridians: Stalwart 01 Server in ~ RAM 110MiB/1GiB CPU 0.33 took 13s
❯ sudo journalctl -xefu stalwart.service
Nov 08 23:40:44 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:44Z DEBUG HTTP request URL (http.request-url) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 40894, url = “/api/schema”
Nov 08 23:40:44 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:44Z DEBUG Cache hit (cache.hit) key = 4294967295, name = “accessToken”
Nov 08 23:40:45 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:45Z DEBUG HTTP request URL (http.request-url) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 40894, url = “/jmap/session”
Nov 08 23:40:45 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:45Z DEBUG Cache hit (cache.hit) key = 4294967295, name = “accessToken”
Nov 08 23:40:45 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:45Z DEBUG Cache miss (cache.miss) key = 4294967295, name = “account”
Nov 08 23:40:45 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:45Z DEBUG HTTP request URL (http.request-url) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 40894, url = “/jmap”
Nov 08 23:40:45 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:45Z DEBUG Cache hit (cache.hit) key = 4294967295, name = “accessToken”
Nov 08 23:40:45 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:45Z DEBUG JMAP method call (jmap.method-call) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 40894, command = “x:MtaOutboundThrottle/query”, accountId = 4294967295, elapsed = 0ms
Nov 08 23:40:45 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:45Z ERROR Registry deserialization error (registry.deserialization-error) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 40894, causedBy = “crates/store/src/registry/mod.rs:115”, value = base64:AAIFbG9jYWwBFExvY2FsIGRlbGl2ZXJ5IHJvdXRl, causedBy = “crates/store/src/dispatch/store.rs:44”, causedBy = “crates/jmap/src/registry/get.rs:319”, accountId = 4294967295, causedBy = “x:MtaOutboundThrottle/get”
Nov 08 23:40:45 stalwart-01-server stalwart[5568]: 2026-11-08T23:40:45Z DEBUG HTTP connection ended (http.connection-end) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 40894, elapsed = 19ms
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG HTTP connection started (http.connection-start) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 49802
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG HTTP request URL (http.request-url) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 49802, url = “/api/schema”
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG Cache hit (cache.hit) key = 4294967295, name = “accessToken”
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG HTTP request URL (http.request-url) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 49802, url = “/jmap/session”
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG Cache hit (cache.hit) key = 4294967295, name = “accessToken”
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG Cache miss (cache.miss) key = 4294967295, name = “account”
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG HTTP request URL (http.request-url) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 49802, url = “/jmap”
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG Cache hit (cache.hit) key = 4294967295, name = “accessToken”
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG JMAP method call (jmap.method-call) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 49802, command = “x:MtaOutboundThrottle/query”, accountId = 4294967295, elapsed = 6ms
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z ERROR Registry deserialization error (registry.deserialization-error) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 49802, causedBy = “crates/store/src/registry/mod.rs:115”, value = base64:AAIFbG9jYWwBFExvY2FsIGRlbGl2ZXJ5IHJvdXRl, causedBy = “crates/store/src/dispatch/store.rs:44”, causedBy = “crates/jmap/src/registry/get.rs:319”, accountId = 4294967295, causedBy = “x:MtaOutboundThrottle/get”
Nov 08 23:44:23 stalwart-01-server stalwart[5568]: 2026-11-08T23:44:23Z DEBUG HTTP connection ended (http.connection-end) listenerId = “http-recovery”, localPort = 9090, remoteIp = 127.0.0.1, remotePort = 49802, elapsed = 115ms

That entry isn’t a throttle at all. The value in the log decodes to your local route (“Local delivery route”), written in the 0.16 format. In 0.16, MtaRoute had the type id that MtaOutboundThrottle has in 1.0, because 1.0 added a few object types and the ids after them shifted. So your 1.0 server is reading settings that 0.16 wrote, and every setting type past that point will fail the same way. Deleting this one entry won’t get you a working server.

A 1.0 server should only ever run on a store written by stalwart-migrate, in a new database separate from the 0.16 one. How did you upgrade from 0.16, and does the PostgreSQL database in your 1.0 config.json contain the tables 0.16 used?

That is how I migrated. But I started stalwart 0.16 by accident.

I did manage to recover by deleting the offending records step by step by analyzing the database :slight_smile: