Every day, over 300 billion emails traverse the internet. They travel between servers you've never heard of, through networks you'll never see, using a protocol designed in 1982. That protocol is SMTP—Simple Mail Transfer Protocol—and despite its age, it remains the backbone of email communication.
Most developers interact with email through high-level APIs that abstract away the underlying mechanics. You call a function, pass some parameters, and email happens. But when things go wrong—and they will go wrong—understanding what's actually happening under the hood is the difference between debugging effectively and flailing in the dark.
The SMTP conversation
SMTP is a text-based protocol, which means you can literally have a conversation with a mail server using telnet or netcat. This isn't just a curiosity—it's a powerful debugging technique. Let's walk through what that conversation looks like.
When your mail server wants to send an email, it first looks up the recipient's mail server using DNS MX records. If you're sending to [email protected], it queries the MX records for example.com to find which server handles mail for that domain.
Then it opens a TCP connection to that server, typically on port 25 (or 587 for submission, or 465 for implicit TLS). The receiving server responds with a greeting—usually a 220 status code and some identifying information.
Your server introduces itself with EHLO (or the older HELO), announcing its hostname. The receiving server responds with a list of extensions it supports—things like STARTTLS for encryption, AUTH for authentication, and SIZE limits.
If both servers support STARTTLS, they negotiate an encrypted connection at this point. This is opportunistic encryption—if it fails, they might fall back to unencrypted transmission (unless one side requires encryption).
Now comes the actual email transmission. Your server sends MAIL FROM with the envelope sender address. Then RCPT TO with the recipient address (repeated for multiple recipients). The receiving server can accept or reject at each step—maybe the recipient doesn't exist, or the sender is blacklisted.
Finally, your server sends DATA, followed by the actual email content (headers and body), terminated by a line containing just a period. The receiving server either accepts the message (250 OK) or rejects it with an error code.
Envelope vs. headers
Here's something that confuses many developers: the addresses in the SMTP conversation (the 'envelope') are separate from the addresses in the email headers. They can be—and often are—different.
The envelope addresses (MAIL FROM and RCPT TO) are used for routing. They determine where the email actually goes and where bounces are sent. The header addresses (From, To, Cc) are what recipients see in their email client.
This separation exists for legitimate reasons. Mailing lists use it: the envelope sender is the list's bounce address, while the header From shows the original author. Forwarding uses it: the envelope changes at each hop, but headers preserve the original sender.
But this separation is also what enables spoofing. An attacker can set the header From to any address they want while using their own server for the envelope. This is why SPF checks the envelope sender, DKIM signs the headers, and DMARC requires alignment between them.
Relaying and smart hosts
In the early days of email, servers would relay messages for anyone. You could connect to any SMTP server and ask it to deliver mail on your behalf. This open relay model was convenient but became untenable as spam exploded.
Today, most SMTP servers are configured to relay only for authenticated users or specific IP ranges. If you try to send through a server you're not authorized to use, you'll get a '550 relay not permitted' error.
Many organizations use a 'smart host' configuration: internal systems send all outbound email to a central server, which handles authentication, queuing, and delivery to the internet. This centralizes email management and makes it easier to implement consistent policies.
Cloud email services work similarly. When you use an API to send email, you're essentially using their servers as a smart host. They handle the SMTP complexity, maintain IP reputation, and deal with delivery issues so you don't have to.
Error codes and what they mean
SMTP uses three-digit status codes, and understanding them helps you diagnose delivery problems.
Codes starting with 2 indicate success. 250 is the standard 'OK' response. 251 means the user isn't local but the server will forward the message.
Codes starting with 4 are temporary failures. 421 means the server is temporarily unavailable. 450 means the mailbox is temporarily unavailable (maybe over quota). 451 is a generic 'try again later.' Your server should retry these automatically.
Codes starting with 5 are permanent failures. 550 is the catch-all rejection—user doesn't exist, relay denied, policy rejection. 551 means the user has moved. 552 means the message is too large. 553 means the address syntax is invalid. 554 is a generic 'transaction failed.'
The second digit provides more context: x0x is syntax, x1x is information, x2x is connections, x5x is mail system status. But in practice, the human-readable text after the code is usually more informative than the code itself.
Modern servers often include extended status codes (like 5.1.1 for 'bad destination mailbox address') and detailed explanations. When debugging delivery issues, always look at the full error message, not just the code.
SMTP in the modern world
SMTP has evolved significantly since 1982, though the core protocol remains recognizable. Several extensions have become essential for modern email.
STARTTLS enables encryption for SMTP connections. It's not perfect—it's opportunistic and vulnerable to downgrade attacks—but it's widely deployed and provides meaningful protection against passive eavesdropping.
SMTP AUTH allows servers to require authentication before accepting mail for relay. This is how you log in to send email through your provider's servers.
CHUNKING and BINARYMIME allow more efficient transmission of large messages and binary content, avoiding the overhead of base64 encoding.
Despite these improvements, SMTP shows its age. It's synchronous and connection-oriented, which doesn't scale well. It has no built-in authentication of senders (hence the need for SPF/DKIM/DMARC). It's text-based, which is great for debugging but inefficient for large volumes.
There have been proposals to replace SMTP with something more modern, but none have gained traction. The installed base is too large, the interoperability requirements too strict. For the foreseeable future, SMTP remains the lingua franca of email.
Frequently asked questions
What's the difference between port 25, 465, and 587?
Port 25 is for server-to-server communication. Port 587 is for client submission (your email client to your provider's server) with STARTTLS. Port 465 is for implicit TLS (encrypted from the start). Most providers now prefer 587 or 465 for client connections.
Why do my emails get rejected with 'relay denied'?
The server doesn't trust you to send through it. Either you're not authenticated, you're trying to send to a domain the server doesn't handle, or your IP isn't in the allowed list. Check your authentication settings.
Can I send email directly without using an email service?
Technically yes, but it's impractical. You'd need to manage IP reputation, handle bounces, implement authentication, deal with rate limiting, and maintain deliverability—all things email services do for you.
What happens if the recipient's server is down?
Your server queues the message and retries periodically. Most servers retry for 4-5 days before giving up and sending a bounce message. The retry schedule varies but typically starts frequent and backs off over time.