Connect by JBRH Open Connect

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.

Status
Reference What this means
Audience
both, developer
Last verified
Product version
6.3.2

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 FROM and RCPT TO commands. This is what the servers act on. The envelope sender becomes the Return-Path and 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 TO and 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 To and 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#

ClassMeaningCorrect response
2xxAccepted at this hopRecord it as accepted, with the server's text. Stop retrying
4xxTemporary failure — greylisting, rate limit, full mailbox, server busyRetry with backoff over hours, not seconds. Give up after a policy window
5xxPermanent failure — no such user, blocked, message refusedDo not retry. Suppress the address if the code says the mailbox does not exist
No reply at allTimeout after DATA was sentUncertain. 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.
  • STARTTLS is 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.