chatmail / chatmail/async-smtp

Whole SMTP Transaction aborted when server refuses one recipient

Open
#72 0 comments 0 reactions 0 assignees View on GitHub
bug enhancement
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.