Repository navigation
Conversation
For multi-recipient deliveries LMTP saves the message for the first recipient and copies that saved mail for the remaining ones, so maildir files can be hardlinked. Delta Chat clients expunge messages right after fetching them, and with remove-500ms-idle-debounce.patch they are notified instantly, so the first recipient's copy is often gone before LMTP is done. All remaining recipients then fail with "451 4.2.0 Message was expunged (received-date)" and Postfix retries later. When a copy from first_saved_mail fails because it was expunged, retry the delivery once from the raw message and use it for the remaining recipients. See chatmail/relay#1064
|
Upstream PR for While building a reproducer for it I found the missing condition: the copy only fails with compression enabled ( The reproducer delivers to 50 recipients while
The config and script are in the upstream PR description. |
Fixes the LMTP part of chatmail/relay#1064.
Problem
For multi-recipient deliveries LMTP saves the message for the first recipient, then copies that saved mail (
first_saved_mail) for every other recipient so the maildir files can be hardlinked. Delta Chat clients expunge messages right after they fetch them, and withremove-500ms-idle-debounce.patchthey are woken up instantly. So the first recipient's copy is often gone before LMTP finishes the list. Every remaining recipient then getsand Postfix defers it. On chatmail.uk this was ~1.4% of all local deliveries (40.7k deferrals in 16 days). Delivery of group messages was delayed by
minimal_backoff_time(5 min by default).Fix
debian/patches/lmtp-retry-expunged-first-saved-mail.patch,src/lmtp/lmtp-local.c:first_saved_mailfails with a temporary error and the source mail isexpunged,lmtp_local_default_do_deliver()returns without replying.lmtp_local_default_deliver()retries that recipient once fromlocal->raw_mailand logsSource mail was expunged, retrying delivery from the raw message.lmtp_local_deliver_to_rcpts()then usesraw_mailfor the remaining recipients. I adjusted the user cleanup condition for this case: oncefirst_saved_mailexists, recipients that saved fromraw_mailstill deinit their ownmail_user.Without the race nothing changes, including hardlinking. Other errors (quota, rejects, …) are replied to as before. The cost when the race does happen: the remaining recipients get full copies instead of hardlinks.
The changelog entry bumps to
+chatmail3. Change it to whatever version suits you.Testing
Built for bookworm amd64 (the same patch on top of the
2.3.21+dfsg1-3+chatmail2release tag, withnocheck) and running on chatmail.uk since 2026-10-22 23:07 UTC. Overnight (23:39 → 06:30 UTC):Message was expungeddeferrals, and no othersave failederrors.Example transaction:
I haven't added a unit test or run the full CI test suite locally, so this PR's CI run is the first one.
Upstream
Dovecot
main(2.4) has the same logic inlmtp-local.c. I'm submitting this here first because relay deploys 2.3.21 from this repository and this exact version is tested in production. It can reach all chatmail relays without waiting for upstream review. A port fordovecot/coremainwill follow as a separate PR, since the surrounding code differs in 2.4 and needs its own build and test run. I'll link it here.