SMTP
SMTP is the protocol one mail server uses to hand a message to another. A client connects, names the sender and the recipients, offers the message, and gets a numeric reply. A 250 means the receiving server has taken responsibility for the message. It does not mean delivered, it does not mean in the inbox, and it certainly does not mean read.
The envelope and the letter are different documents#
This is the single most useful thing to understand about SMTP, and it explains SPF, forwarding, bounces and most "the From address is wrong" questions at once.
MAIL FROM:<bounces@example.net> <- envelope sender (Return-Path)
RCPT TO:<someone@example.net> <- envelope recipient
DATA
From: Acme Traders <hello@example.net> <- header, what a reader sees
To: Purchasing <someone@example.net>
Subject: Your order
.- Envelope
- The
MAIL FROMandRCPT TOcommands. This is what the servers act on. The envelope sender becomes theReturn-Pathand receives bounces. - Headers
From,To,Cc,Subject. This is what the reader's client displays, and it need not match the envelope at all.- Bcc
- Not a header on the delivered message. It is an extra
RCPT TOand nothing else, which is why a Bcc recipient sees no trace of themselves. To:with nobody in it- Perfectly legal. A mailing list message often has a list address in
Toand your address only in the envelope.
SPF authorises the *envelope* sender. DKIM signs the *message*, headers included. DMARC exists to require that one of those two aligns with the domain in the visible From header — which is the only part a reader ever sees.
Reading the reply codes#
| Class | Meaning | Correct response |
|---|---|---|
2xx | Accepted at this hop | Record it as accepted, with the server's text. Stop retrying |
4xx | Temporary failure — greylisting, rate limit, full mailbox, server busy | Retry with backoff over hours, not seconds. Give up after a policy window |
5xx | Permanent failure — no such user, blocked, message refused | Do not retry. Suppress the address if the code says the mailbox does not exist |
| No reply at all | Timeout after DATA was sent | Uncertain. The message may have been accepted. Retrying risks a duplicate |
Later signals are separate events with separate mechanics. A bounce arrives as a new message to the Return-Path address, often minutes or days afterwards, and a complaint arrives through a provider's feedback loop. Neither is an SMTP reply, and neither can be waited for at send time.
Ports, transport security and submission#
- 587 — submission. What a client uses to send its own mail, authenticated, upgrading to TLS with
STARTTLS. This is the correct port for an application sending through a provider. - 465 — implicit TLS submission. TLS from the first byte. Widely supported and, where offered, marginally safer because there is no plaintext moment to strip.
- 25 — relay between servers. Not for client submission, and blocked outbound by most networks. A configuration that sends on 25 usually works in a data centre and fails everywhere else.
STARTTLSis strippable. An attacker on the path can remove the offer and the client may fall back to plaintext. MTA-STS and DANE exist to stop that; neither is universal.
Does Connect use SMTP?#
Used, for every mailbox connected as IMAP/SMTP. A workspace may connect Gmail through the Gmail API, Microsoft Graph, or any IMAP/SMTP server; providers.build() selects the transport from the mailbox's own configuration, and the rest of Connect does not know or care which was chosen.
Whatever the transport, sending goes through one boundary — outbound.py — used identically by the agent and by a person clicking send. That boundary is where the reply code is turned into a state, and it holds a rule this page has been building towards: a message is reported as sent only when the provider has acknowledged it. Where there is no acknowledgement, the state is recorded as uncertain and shown as uncertain, because re-sending on a maybe is how a customer receives the same reply twice.
Two consequences follow for anyone reading a conversation. A message marked sent has a provider acknowledgement behind it rather than an optimistic write. A message marked uncertain is a real state with a real cause, not a rendering glitch, and it is the state SMTP itself produces when a connection dies at the wrong moment.
Questions#
Does a 250 response mean the message reached the inbox?
No. It means the server you handed it to accepted responsibility for it. That server may still route it to spam, apply a filter, forward it somewhere, or bounce it minutes later. Delivery to a folder is a decision made after the SMTP conversation has ended, and nothing in the protocol reports it back.
Why can the From header differ from the envelope sender?
Because they serve different purposes: the envelope routes and receives bounces, the header addresses the reader. Legitimate mail relies on this — bounce handling, mailing lists and forwarding all depend on the two differing. It is also why authentication needed DMARC, which is the piece that ties the visible From back to something authorised.
Should an application retry after a 4xx?
Yes, with backoff, and with a limit. Greylisting deliberately answers 4xx to a first attempt and accepts the second, so a client that does not retry loses mail to a very common anti-spam technique. Retrying a 5xx, by contrast, achieves nothing and damages sender reputation.