The wire

What actually happens in the twenty seconds after you press send

The code that has this conversation with another organisation's mail server is ours, and it is named after this domain. This page walks the session it runs, in the order it runs it.

Handing your outbound mail to an outside marketing service means handing that service your contact list. The reason to run the dialogue yourself is not that it is clever — it is not, it is a 1980s line protocol — it is that the list then has one fewer place to be.

Everything below is injected rather than hardcoded: the socket, the encryption upgrade, the address lookup. That is why the session can be exercised end to end against a scripted fake server with no network at all. It is a property of how the module is built, and it is not the same thing as a claim about production traffic, which is covered further down.

Step by step

8 steps, in the order the code performs them

Pick a step. Each panel shows what goes on the wire, what comes back, and the decision the code makes about it.

1. Greeting

The far side speaks first, and we are allowed to be refused here

> (listen)
< 220 mail.example.org ESMTP

A mail server greets the connection before we say anything. A 220 means carry on. Anything else ends the session there, and the reply is classified rather than ignored: a 4xx becomes a deferral we will retry, a 5xx becomes a bounce we will not.

The important part is what does not happen. There is no branch that treats a refused greeting as a partial success, because a message that never got past the greeting was never sent and the record should say so.

mitto-smtp-transport.ts:298 (runDialogue, step 1)

2. EHLO

We introduce ourselves and read back what the server can do

> EHLO mitto.localhost
< 250-STARTTLS / 250 OK

The introduction asks the far side to list its capabilities. Those come back as a multiline reply, and parsing one correctly is fussier than it looks: non-final lines are marked with a hyphen after the code and the final line with a space, and the code that matters is the one on the last line.

That parser is a pure, total function with no socket in it, which is why it can be tested against captured replies rather than against a live server that may answer differently tomorrow.

mitto-smtp-transport.ts:298 (step 2) · :407 parseReplyLines

3. STARTTLS

If the server advertises encryption, we take it -- and never quietly decline it

> STARTTLS
< 220 ready to start TLS

When the capability list includes STARTTLS the session is upgraded to an encrypted channel and the introduction is repeated over it, because the capabilities advertised before the upgrade cannot be trusted afterwards. That repeat is required by the protocol and it is easy to skip; it is not skipped here.

If the upgrade is refused or fails, the attempt is recorded as a deferral. It does not fall back to sending the message in the clear. A quiet downgrade is the kind of thing that never shows up in a delivery report, which is exactly why it has to be a rule in the code and not a preference.

mitto-smtp-transport.ts:237 offersStartTls · :298 (step 3)

4. MAIL FROM

The envelope sender is a per-message address, not the address a human reads

> MAIL FROM:<[email protected]>
< 250 OK

There are two senders on an email and most people only ever see one. The from-line a recipient reads is a header. The envelope sender is the address the receiving server will send a failure notice to, and here it is unique per message: it carries the queue-row identifier for this exact send.

That means a bounce comes back attributable to one message instead of arriving in a shared mailbox where nobody can tell which of nine hundred sends it belongs to. It is a small decision with a large effect on whether a delivery problem is diagnosable at all.

mitto-smtp-transport.ts:176 buildVerpEnvelopeFrom

5. RCPT TO

One recipient, stated to the server, and refusable by it

> RCPT TO:<[email protected]>
< 250 OK

The recipient is named to the server, which may accept or refuse it. A refusal here is the most informative failure in the whole dialogue, because it is the server telling you something about that specific address rather than about your message.

A 5xx at this step is a bounce and the address should stop being mailed. That is not this module's decision to make -- it hands the classified attempt back and the address-level suppression stop is what acts on it.

mitto-smtp-transport.ts:298 (step 5)

6. DATA

A 354 is the one reply in the session that means neither yes nor no

> DATA
< 354 end with <CRLF>.<CRLF>

DATA asks permission to start sending the message body, and the expected answer is a 354, which is an intermediate code: not a success, not a failure, an instruction to continue. Code that treats every 3xx as a soft error gets this wrong and defers messages that were about to be accepted.

Anything other than 354 here is mapped by its own class, so an overloaded server saying 421 defers and a policy refusal saying 550 bounces.

mitto-smtp-transport.ts:298 (step 6)

7. The body

Dot-stuffing, the signature if there is one, and the terminator

> <headers> <blank line> <body> CRLF . CRLF
< 250 queued as ...

The message is terminated by a line containing a single dot, which creates an obvious problem: a line in your text that happens to start with a dot would end the message early. The protocol's answer is to double a leading dot on the way out, and the receiving server undoes it. Skip that and a paragraph beginning with an ellipsis truncates the email.

If a signer is wired in, its header is prepended here. If there is no key, the message goes out UNSIGNED and is recorded as unsigned. There is no code path that writes a signature it could not compute.

mitto-smtp-transport.ts:201 dotStuff · :212 composeMessage

8. The verdict

The reply to the body is the whole answer, and QUIT cannot change it

> QUIT
< 221 closing

The reply that follows the message body is the authoritative outcome: 2xx delivered, 4xx deferred, 5xx bounced. QUIT is a courtesy afterwards and its failure is caught and discarded, because a connection that dropped after the server already accepted the message did not un-accept it.

Getting that order wrong is a real and common bug. It turns an accepted message into a retry, and the recipient gets it twice.

mitto-smtp-transport.ts:298 (step 8)

The 8-step SMTP session our own transport runs, in the order the code performs it: the server greets, we introduce ourselves, we upgrade to an encrypted channel when it is offered, we state the envelope sender and the recipient, we ask to send the body, we send it, and the reply to the body is the verdict.Our senderTheir mail server1. Greeting220 mail.example.org ESMTP2. EHLO250-STARTTLS / 250 OK3. STARTTLS220 ready to start TLS4. MAIL FROM250 OK5. RCPT TO250 OK6. DATA354 end with &lt;CRLF&gt;.&lt;CRLF&gt;7. The body250 queued as ...8. The verdict221 closing
Figure 1. Drawn from the same step table that writes the panels above, so the picture cannot claim a step the prose does not describe.

Reading the answer

Five kinds of reply, and what each one entitles us to say

A delivery report is only worth reading if the thing that wrote it was strict about what a reply code means. These are the five outcomes and the conclusion each one licenses.

2xx

The server accepted responsibility for the message.

We do: Record a delivery attempt as delivered, and seal the idempotency key so a retry cannot send it twice.

We do not: Claim it reached an inbox. Acceptance and placement are different facts and only the first one is ours.

354

Intermediate: send the body now.

We do: Write the dot-stuffed message and wait for the real verdict.

We do not: Treat it as a soft failure. It is neither success nor error, and mapping it to either is a bug.

4xx

Try again later -- the server is busy, greylisting, or temporarily unhappy.

We do: Record a deferral, which keeps the message eligible for a later send with backoff.

We do not: Give up, and never mark it delivered.

5xx

Permanent refusal -- no mailbox, policy block, or a rejected sender.

We do: Record a bounce so the address-level suppression stop can act on it.

We do not: Retry it into the ground. Repeatedly mailing a dead address is how a domain earns a reputation problem for everyone else on it.

0

We never got a usable reply: no socket factory, a refused connection, or the peer hung up mid-dialogue.

We do: Record a deferral with the reason attached, including the plain string 'not provisioned' when the transport has no socket factory at all.

We do not: Fabricate an outcome. There is no branch anywhere in the module that returns delivered without a 2xx from a real server.

The distinction that matters most is the first one. A 2xx means a server accepted responsibility for the message. It does not mean the message reached an inbox, and no software on earth can tell you that it did. A product that reports "delivered" and means "accepted" is not lying so much as quietly redefining a word its customer already understood.

Around the session

The four things that surround a send

A retry after a slow acceptance must not become a second email

Every send derives a stable key from the channel, the address, and the rendered message, so the same logical message always presents the same key however many times a runner attempts it.

A success seals the key; a failure does not. That asymmetry is the whole point: a transient failure stays legitimately retryable, while a message the receiving server already accepted cannot be sent twice because a socket hiccuped on the way back.

packages/notifications/src/delivery/idempotency.ts:45 (deriveIdempotencyKey)

An address that has told us to stop is stopped on every lane

A hard bounce, a complaint, or a global opt-out binds to the ADDRESS rather than to a campaign, so it is honoured identically by every caller, including a one-off send that never went near a planner.

It is fail-closed. If the suppression store cannot answer, the send is blocked rather than attempted. That costs something — a store outage stops mail — and it is the choice we would make again, because sending into uncertainty about a complaint address damages deliverability for everything else on the domain.

packages/notifications/src/delivery/suppression.ts:53 (suppressionGuard)

An unsubscribe link that stops working is worse than none

The unsubscribe target is a signed, stateless token with no expiry, because someone may click a link in a year-old message and it has to still work.

That is a deliberate design decision recorded in the module rather than an accident of implementation, and it is the kind of thing that only ever gets noticed when it is missing.

packages/notifications/src/unsubscribe-token.ts:64 (mintUnsubscribeToken)

What happens today, with the latch off

A transport built without a socket factory is not provisioned. Its delivery call returns a deferred attempt carrying the plain reason not provisioned, and deferred is the retry class, so the message stays eligible for a real send later.

It never fakes a delivery. That sentence is worth being precise about, because it is the one a buyer should check: there is no code path in the module that returns a delivered outcome without a success reply from a real server.

packages/notifications/src/mitto-smtp-transport.ts:262 (provisioned) · apps/api/src/services/comms-send-transport.ts:82 (honestOffOwnedMta)

Built and not built

The table where we lose two rows on purpose

Product categories, not named vendors. Two rows go against us, and they are the two a buyer most needs to know before signing anything.

Every cell states its verdict in words, so the table reads correctly with the stylesheet removed and with no colour perception at all.
Question a buyer should askA bulk marketing toolA big free providerThis mailbox
Who holds your contact listThe marketing vendorYour provider, plus their partnersOur own private system
Bounce attributable to one messageUsually, inside their dashboardOne shared bounce mailboxPer-message envelope sender
Encryption declined quietly if the upgrade failsVaries by productVaries by productNever -- the attempt defers instead
'Delivered' means accepted, not placedReported as deliveredReported as deliveredRecorded as accepted, and said so
Runs a campaign builder and open-rate dashboardYesSometimesNo -- and that is a real loss for some buyers
Sending is live todayYesYesNo -- queued, and recorded as queued

If a comparison table wins every row, it was built backwards from its conclusion. The last two rows here are honest losses: there is no campaign tooling, and we are not accepting mail yet.