Carry the submitter with AUTH= on MAIL FROM
RFC 4954 §5, which is how a relay says who originally submitted the
message it is carrying. `protocol.Submitter` is the parameter: a mailbox,
or the two characters `<>` that mean "I do not know". Both sides handle
it, and the xtext codec DSN already needed is what encodes the mailbox --
the `=` in an address like e=mc2@example.com would otherwise end the
parameter.
The interesting rule is §5's, and it is the opposite of what one would
guess: a server advertising AUTH **must accept the parameter even from a
client that has not authenticated**, and must then behave as though `<>`
had been sent. Taking it and disbelieving it, rather than refusing it, is
what keeps a relay from having to know in advance whether it will be
trusted. So the server records the claim as `.unknown` when the session
is unauthenticated, and a `.mailbox` reaching a handler always means an
authenticated peer asserted it -- `Envelope.authenticated_as` says which
peer, which is the other half of deciding whether to believe it.
A server that advertises no mechanisms at all is in a different position:
it never offered the extension, so the parameter is simply unrecognized
and gets 555.
`<>` is a claim rather than an absence, which is why the client spells it
`.unknown` rather than leaving the parameter off: a server that receives
nothing learns nothing, where one that receives `<>` learns the peer
considered the question.
Verified against exim, which decoded the xtext, believed an authenticated
peer, and kept the value -- the interop test reads it back out of the
delivered message, exim having no log selector that shows it. And against
postfix, which advertises no AUTH on that port, where the client refuses
to send the parameter at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDrB41sGu5k1ubD1ufbxqC
Carry the submitter with AUTH= on MAIL FROM
RFC 4954 §5, which is how a relay says who originally submitted the
message it is carrying. `protocol.Submitter` is the parameter: a mailbox,
or the two characters `<>` that mean "I do not know". Both sides handle
it, and the xtext codec DSN already needed is what encodes the mailbox --
the `=` in an address like e=mc2@example.com would otherwise end the
parameter.
The interesting rule is §5's, and it is the opposite of what one would
guess: a server advertising AUTH **must accept the parameter even from a
client that has not authenticated**, and must then behave as though `<>`
had been sent. Taking it and disbelieving it, rather than refusing it, is
what keeps a relay from having to know in advance whether it will be
trusted. So the server records the claim as `.unknown` when the session
is unauthenticated, and a `.mailbox` reaching a handler always means an
authenticated peer asserted it -- `Envelope.authenticated_as` says which
peer, which is the other half of deciding whether to believe it.
A server that advertises no mechanisms at all is in a different position:
it never offered the extension, so the parameter is simply unrecognized
and gets 555.
`<>` is a claim rather than an absence, which is why the client spells it
`.unknown` rather than leaving the parameter off: a server that receives
nothing learns nothing, where one that receives `<>` learns the peer
considered the question.
Verified against exim, which decoded the xtext, believed an authenticated
peer, and kept the value -- the interop test reads it back out of the
delivered message, exim having no log selector that shows it. And against
postfix, which advertises no AUTH on that port, where the client refuses
to send the parameter at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDrB41sGu5k1ubD1ufbxqC
Carry the submitter with AUTH= on MAIL FROM
RFC 4954 §5, which is how a relay says who originally submitted the
message it is carrying. `protocol.Submitter` is the parameter: a mailbox,
or the two characters `<>` that mean "I do not know". Both sides handle
it, and the xtext codec DSN already needed is what encodes the mailbox --
the `=` in an address like e=mc2@example.com would otherwise end the
parameter.
The interesting rule is §5's, and it is the opposite of what one would
guess: a server advertising AUTH **must accept the parameter even from a
client that has not authenticated**, and must then behave as though `<>`
had been sent. Taking it and disbelieving it, rather than refusing it, is
what keeps a relay from having to know in advance whether it will be
trusted. So the server records the claim as `.unknown` when the session
is unauthenticated, and a `.mailbox` reaching a handler always means an
authenticated peer asserted it -- `Envelope.authenticated_as` says which
peer, which is the other half of deciding whether to believe it.
A server that advertises no mechanisms at all is in a different position:
it never offered the extension, so the parameter is simply unrecognized
and gets 555.
`<>` is a claim rather than an absence, which is why the client spells it
`.unknown` rather than leaving the parameter off: a server that receives
nothing learns nothing, where one that receives `<>` learns the peer
considered the question.
Verified against exim, which decoded the xtext, believed an authenticated
peer, and kept the value -- the interop test reads it back out of the
delivered message, exim having no log selector that shows it. And against
postfix, which advertises no AUTH on that port, where the client refuses
to send the parameter at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDrB41sGu5k1ubD1ufbxqC
Carry the submitter with AUTH= on MAIL FROM
RFC 4954 §5, which is how a relay says who originally submitted the
message it is carrying. `protocol.Submitter` is the parameter: a mailbox,
or the two characters `<>` that mean "I do not know". Both sides handle
it, and the xtext codec DSN already needed is what encodes the mailbox --
the `=` in an address like e=mc2@example.com would otherwise end the
parameter.
The interesting rule is §5's, and it is the opposite of what one would
guess: a server advertising AUTH **must accept the parameter even from a
client that has not authenticated**, and must then behave as though `<>`
had been sent. Taking it and disbelieving it, rather than refusing it, is
what keeps a relay from having to know in advance whether it will be
trusted. So the server records the claim as `.unknown` when the session
is unauthenticated, and a `.mailbox` reaching a handler always means an
authenticated peer asserted it -- `Envelope.authenticated_as` says which
peer, which is the other half of deciding whether to believe it.
A server that advertises no mechanisms at all is in a different position:
it never offered the extension, so the parameter is simply unrecognized
and gets 555.
`<>` is a claim rather than an absence, which is why the client spells it
`.unknown` rather than leaving the parameter off: a server that receives
nothing learns nothing, where one that receives `<>` learns the peer
considered the question.
Verified against exim, which decoded the xtext, believed an authenticated
peer, and kept the value -- the interop test reads it back out of the
delivered message, exim having no log selector that shows it. And against
postfix, which advertises no AUTH on that port, where the client refuses
to send the parameter at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDrB41sGu5k1ubD1ufbxqC
Rename the project to zig-smtp
It sits beside zig-pop3, zig-ftp, zig-scram and zig-sasl, and "zsmtp" was
the odd one out.
Three names come out of it rather than one, following what those siblings
do. The repository and the package are `zig-smtp`; the Zig module is
`smtp`, so callers write `@import("smtp")` the way zig-pop3's callers
write `@import("pop3")`; and the demo CLI is `zig-smtp`, matching zig-ftp
rather than pop3's bare `pop3`, because `smtp` is too generic a name to
put on somebody's PATH.
The manifest fingerprint had to change with the package name -- Zig
derives it from that name and refuses the old one -- so this is a new
package as far as the package manager is concerned, not a renamed one.
The Radicle identity was renamed in place, so the RID is unchanged and
every `rad clone rad:z3ZKHgoDKEue8FT7sV6fHZdtjxRx1` in the wild still
works. The Tangled mirror could not be renamed and was recreated.
The published documentation moves with it, from jeff.jcollie.page/zsmtp/
to jeff.jcollie.page/zig-smtp/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDrB41sGu5k1ubD1ufbxqC