A refused recipient drops the message for the rest of the envelope
- Dominant language
- Rust
- Stars
- 929
- Forks
- 143
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 53
Description
When chatmail clients are used **with non-relay setups** there is a severe problem: `async-smtp` aborts the transaction at the first refused `RCPT TO`. Core then treats a permanent refusal of a single recipient as a permanent failure for everyone: the chunk loop in `send_msg_to_smtp` stops, the message is marked failed, and [never retried for anyone][deleted]. A transient refusal blocks everyone just the same, but keeps retrying the whole envelope.
Note that chatmail relays accept all recipients, and later asynchronously send a DSN failure message which core processes, except for broadcast channels, where [`handle_ndn` skips them][ndn].
Once [async-smtp reports per-recipient status](https://github.com/chatmail/async-smtp/issues/72), core needs to deal with per- recipient status, dropping permanent failures and accepted recipients from the envelope, and keeping only the transient ones.
Observed 2026-08-29, https://github.com/chatmail/core/pull/8621#pullrequestreview-5057803047 :
```
src/smtp.rs:206: SMTP failed to send: Permanent(Response { ...
message: ["5.1.1 : Recipient address rejected: User unknown in virtual mailbox table"] }).
src/smtp.rs:239: Permanent error, message sending failed.
```
Unrelated but visible in the same log: [on a send failure][markfailed] core loads the message to mark it failed, which for `insert_into_smtp()` tombstones (keyupdates, securejoin) always fails and logs `Failed to load Msg#319115 to mark it as failed: Message Msg#319115 does not exist.` at `error!`.
[pin]: https://github.com/chatmail/core/blob/main/Cargo.toml#L49
[deleted]: https://github.com/chatmail/core/blob/main/src/smtp.rs#L474-L477
[ndn]: https://github.com/chatmail/core/blob/main/src/mimeparser.rs#L2602-L2603
[markfailed]: https://github.com/chatmail/core/blob/main/src/smtp.rs#L308-L326
Contributor guide
Research direction
Start in src/smtp.rs at send_msg_to_smtp and the chunk-loop handling around lines 474-477, then review async-smtp issue 72 for the per-recipient status API. Check the related failure path around lines 308-326 and the broadcast handling in src/mimeparser.rs. Done means permanent refusals and accepted recipients no longer affect the remaining envelope, while transient refusals remain retryable.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 50/100