chatmail / chatmail/async-smtp
Whole SMTP Transaction aborted when server refuses one recipient
- Dominant language
- Rust
- Stars
- 74
- Forks
- 16
- PR merge metrics
- No merged PRs in 30d
Description
`async-smtp 0.10.2` returns at the first refused `RCPT TO`, [pipelined][pipelined] and [non-pipelined][non-pipelined]. Recipients the server already accepted get no delivery attempt, and the rest are never offered.
The pipelined path violates [RFC 2920 §3.1][rfc2920-3.1]:
> Client SMTP implementations that employ pipelining MUST check ALL statuses associated with each command in a group. For example, if none of the RCPT TO recipient addresses were accepted the client must then check the response to the DATA command -- the client cannot assume that the DATA command will be rejected just because none of the RCPT TO commands worked.
`read_response().await?` returns on the first refusal, leaving the remaining statuses unread.
Partial success is a defined outcome, [RFC 5321 §4.4][rfc5321-4.4]:
> The server must give special treatment to cases in which the processing following the end of mail data indication is only partially successful. This could happen if, after accepting several recipients and the mail data, the SMTP server finds that the mail data could be successfully delivered to some, but not all, of the recipients. In such cases, the response to the DATA command MUST be an OK reply.
Both permanent (5xx) and transient (4xx) refusals should be skipped, and reported back separately: permanent means the address can be dropped, transient means only that address needs retrying. Handing back the reply itself also preserves any [RFC 3463 §3.2][rfc3463-3.2] enhanced status code, `5.1.1` in the case below.
Observed on a keyupdate, https://github.com/chatmail/core/pull/8621#pullrequestreview-5057803047 : `5.1.1 <...>: Recipient address rejected: User unknown in virtual mailbox table` failed the whole send. Chatmail relays accept all recipients and send asynchronous DSNs later, so they do not hit it; relays that validate during submission do.
[pipelined]: https://github.com/chatmail/async-smtp/blob/v0.10.2/src/smtp_client.rs#L249-L251
[non-pipelined]: https://github.com/chatmail/async-smtp/blob/v0.10.2/src/smtp_client.rs#L261-L267
[rfc2920-3.1]: https://datatracker.ietf.org/doc/html/rfc2920#section-3.1
[rfc5321-4.4]: https://datatracker.ietf.org/doc/html/rfc5321#section-4.4
[rfc3463-3.2]: https://datatracker.ietf.org/doc/html/rfc3463#section-3.2
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/smtp_client.rs at the pipelined lines 249-251 and non-pipelined lines 261-267, then trace how read_response().await? handles RCPT TO replies. Use RFC 2920 §3.1 and RFC 5321 §4.4 to verify that all statuses are consumed and accepted recipients still reach DATA. Done means permanent and transient refusals are skipped and reported separately with their replies.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100