Loading content...
Every SMTP transaction is a structured conversation between client and server. This conversation follows precise rules, using a defined set of commands issued by the client and response codes returned by the server. Mastering this vocabulary is essential for anyone implementing, debugging, or securing email systems.
Unlike casual human conversation, SMTP dialogues are strictly ordered and predictable. Each command serves a specific purpose in establishing the email envelope, transferring message content, or managing the session. Understanding these commands transforms email from a mysterious "it just works" service into a transparent, debuggable system.
By the end of this page, you will master every standard SMTP command—HELO, EHLO, MAIL FROM, RCPT TO, DATA, RSET, VRFY, EXPN, HELP, NOOP, and QUIT. You'll understand the response code system, learn proper command sequences, and gain the ability to manually conduct SMTP sessions for testing and troubleshooting.
SMTP commands follow a simple, consistent structure designed for both human readability and machine parsing. Understanding this structure is fundamental to working with the protocol.
Command Syntax:
Every SMTP command consists of:
\r )COMMAND [SP parameter [SP parameter...]] CRLF
Critical Syntax Rules:
HELO, helo, and HeLo are equivalent, though uppercase is conventional<user@domain.com>Parameter Encoding:
Email addresses in SMTP commands use a specific format called a "reverse-path" or "forward-path":
MAIL FROM:<sender@example.com>
RCPT TO:<recipient@example.com>
The angle brackets are mandatory in strict SMTP. Some implementations accept bare addresses (MAIL FROM:sender@example.com), but this violates the specification and can cause interoperability issues.
Extended Parameters (ESMTP):
When using Extended SMTP (ESMTP), commands can include additional keyword-value parameters:
MAIL FROM:<sender@example.com> SIZE=1048576 BODY=8BITMIME
RCPT TO:<recipient@example.com> NOTIFY=SUCCESS,FAILURE
These extensions enable features like message size declaration, delivery status notifications, and internationalized email addresses.
Many developers accidentally use only LF ( ) as line terminators, especially on Unix-like systems. While some servers tolerate this, RFC 5321 requires CRLF (\r ). Always use full CRLF sequences in SMTP implementations to ensure compatibility.
After every command, the SMTP server returns a response consisting of a three-digit numeric code and a human-readable message. Understanding this response system is crucial for proper client behavior and error handling.
Response Structure:
XYZ [SP text] CRLF
| Code | Category | Meaning | Client Action |
|---|---|---|---|
| 1xx | Positive Preliminary | Command accepted, awaiting more data | Continue with next part of command |
| 2xx | Positive Completion | Command completed successfully | Proceed to next command |
| 3xx | Positive Intermediate | Command accepted, more input expected | Send the requested input |
| 4xx | Transient Negative | Temporary failure; retry may succeed | Retry later (queue the message) |
| 5xx | Permanent Negative | Permanent failure; do not retry | Generate bounce, notify sender |
| Code | Meaning | Typical Context |
|---|---|---|
| 220 | Service ready | Initial server greeting |
| 221 | Service closing | Response to QUIT command |
| 250 | Requested action OK | Success for most commands |
| 251 | User not local; will forward | Recipient on different server |
| 354 | Start mail input | Response to DATA command |
| 421 | Service not available | Server shutting down, try later |
| 450 | Mailbox unavailable (temp) | Mailbox busy or locked |
| 451 | Local error in processing | Server-side temporary issue |
| 452 | Insufficient system storage | Disk full, retry later |
| 500 | Syntax error, command unrecognized | Invalid command sent |
| 501 | Syntax error in parameters | Malformed arguments |
| 502 | Command not implemented | Server doesn't support command |
| 503 | Bad sequence of commands | Command out of order |
| 550 | Mailbox unavailable (perm) | User doesn't exist, no access |
| 551 | User not local | Try different server |
| 552 | Storage allocation exceeded | Message too large |
| 553 | Mailbox name not allowed | Invalid address syntax |
| 554 | Transaction failed | General permanent failure |
Multi-line Responses:
Servers can send multi-line responses for extended information. In multi-line responses, all lines except the last use a hyphen after the code instead of a space:
250-mail.example.com Hello client.example.org
250-SIZE 52428800
250-8BITMIME
250-STARTTLS
250-AUTH LOGIN PLAIN
250 HELP
Enhanced Status Codes (RFC 3463):
Modern SMTP servers often include enhanced status codes providing more detail:
550 5.1.1 <unknown@example.com>: Recipient address rejected: User unknown
The 5.1.1 is the enhanced code, where:
When parsing SMTP responses, always key on the numeric code, not the text message. The text varies between implementations and is meant for human administrators. A robust client makes decisions based solely on the three-digit code.
Every SMTP session begins with the client identifying itself to the server. This identification phase uses either the original HELO command or the extended EHLO command, which enables additional protocol features.
HELO — Original Greeting Command
The HELO command is the original SMTP greeting from RFC 821. It provides a simple client identification:
C: HELO sender.example.org
S: 250 mail.receiver.com Hello sender.example.org
Syntax: HELO <domain>
<domain> should be the fully qualified domain name (FQDN) of the clientHELO [192.168.1.100]EHLO — Extended SMTP Greeting
The EHLO (Extended HELO) command, introduced in RFC 1869, is the modern greeting that negotiates extended capabilities:
C: EHLO sender.example.org
S: 250-mail.receiver.com Hello sender.example.org
S: 250-SIZE 52428800
S: 250-8BITMIME
S: 250-STARTTLS
S: 250-ENHANCEDSTATUSCODES
S: 250-PIPELINING
S: 250-CHUNKING
S: 250-AUTH LOGIN PLAIN CRAM-MD5
S: 250 SMTPUTF8
Syntax: EHLO <domain>
The server's multi-line response lists all supported extensions. The client can then use extensions that appear in this list.
| Extension | Meaning | Purpose |
|---|---|---|
| SIZE | Message size declaration | Allows client to declare message size upfront |
| 8BITMIME | 8-bit MIME transport | Enables 8-bit message bodies without encoding |
| STARTTLS | TLS encryption upgrade | Allows upgrading connection to TLS |
| AUTH | Authentication support | Enables client authentication (LOGIN, PLAIN, etc.) |
| PIPELINING | Command pipelining | Allows sending multiple commands without waiting |
| ENHANCEDSTATUSCODES | Extended status codes | Provides detailed error classification |
| CHUNKING | BDAT command support | Alternative data transfer for large messages |
| SMTPUTF8 | UTF-8 addresses | Enables internationalized email addresses |
| DSN | Delivery Status Notifications | Enables detailed delivery reports |
12345678910111213141516171819202122232425262728
# Modern client behavior:# 1. Try EHLO first (for extension negotiation)C: EHLO client.example.orgS: 250-server.example.com Hello client.example.orgS: 250-STARTTLSS: 250 AUTH PLAIN LOGIN # Server supports STARTTLS, so client upgrades to TLSC: STARTTLSS: 220 Ready to start TLS# ... TLS handshake occurs ... # After TLS, client MUST re-issue EHLOC: EHLO client.example.orgS: 250-server.example.com Hello client.example.orgS: 250-AUTH PLAIN LOGINS: 250 SIZE 52428800 # Now client can authenticate and send mail # ---# Fallback for legacy servers that don't support EHLO:C: EHLO client.example.orgS: 500 Command not recognized # Fall back to HELOC: HELO client.example.orgS: 250 Hello client.example.orgAfter upgrading to TLS with STARTTLS, the client MUST send EHLO again. This is because the TLS session is essentially a new connection layer, and the server may advertise different capabilities after encryption is established (e.g., AUTH may only appear after TLS).
The envelope of an email message—distinct from the message headers—is established using two critical commands: MAIL FROM and RCPT TO. These commands define the actual routing information used by SMTP servers, which may differ from what appears in the message headers.
The Envelope vs. Headers Distinction:
This is a crucial concept many email users don't understand:
MAIL FROM: The address to which bounces (non-delivery reports) are sent; used only by SMTP servers, never shown to recipientsFrom: What recipients see as the sender; can be different from envelope senderRCPT TO: The actual delivery destination(s); determines where the message is routedTo/Cc: What recipients see; may not match actual recipients (e.g., Bcc recipients)MAIL FROM Command:
The MAIL FROM command initiates a mail transaction and specifies the return path for bounces.
Syntax: MAIL FROM:<reverse-path> [SP extension-params]
C: MAIL FROM:<alice@sender.example.org>
S: 250 2.1.0 Sender OK
Extension Parameters:
SIZE=<bytes> — Declares message size upfront, allowing server to reject before data transferBODY=7BIT|8BITMIME — Declares message body encodingRET=FULL|HDRS — For DSN: return full message or headers only on bounceENVID=<xtext> — Envelope ID for trackingC: MAIL FROM:<alice@example.org> SIZE=1048576 BODY=8BITMIME
S: 250 2.1.0 Sender OK
RCPT TO Command:
The RCPT TO command specifies a recipient. Each recipient requires a separate RCPT TO command.
Syntax: RCPT TO:<forward-path> [SP extension-params]
C: RCPT TO:<bob@receiver.example.com>
S: 250 2.1.5 Recipient OK
C: RCPT TO:<carol@receiver.example.com>
S: 250 2.1.5 Recipient OK
C: RCPT TO:<unknown@receiver.example.com>
S: 550 5.1.1 User unknown
Extension Parameters:
NOTIFY=NEVER|SUCCESS|FAILURE|DELAY — DSN notification conditionsORCPT=<addr-type>;addr — Original recipient for DSN trackingC: RCPT TO:<bob@example.com> NOTIFY=SUCCESS,FAILURE
S: 250 2.1.5 Recipient OK
| Code | Enhanced | Meaning | Client Action |
|---|---|---|---|
| 250 | 2.1.5 | Recipient accepted | Continue with more RCPT TO or DATA |
| 251 | 2.1.5 | User not local; will forward | Message will be relayed; continue |
| 450 | 4.1.1 | Mailbox unavailable (temp) | Retry later |
| 451 | 4.3.5 | Server error; try later | Retry later |
| 452 | 4.5.3 | Too many recipients | Send current batch, then continue |
| 550 | 5.1.1 | User unknown | Remove from recipient list; notify sender |
| 551 | 5.1.6 | User not local; try elsewhere | Try specified alternate address |
| 552 | 5.2.2 | Mailbox full | Depends on policy; may retry later |
| 553 | 5.1.3 | Mailbox name not allowed | Address syntax invalid |
A null return path (MAIL FROM:<>) is valid and used for bounce messages. This prevents infinite bounce loops—bounces themselves can't bounce. When implementing SMTP, always accept MAIL FROM:<> for incoming bounce messages.
The DATA command is where the actual message content—headers and body—is transmitted. This is the heart of SMTP, where the email message moves from sender to receiver.
DATA Command Protocol:
C: DATA
S: 354 Start mail input; end with <CRLF>.<CRLF>
C: From: Alice <alice@sender.example.org>
C: To: Bob <bob@receiver.example.com>
C: Subject: Meeting Tomorrow
C: Date: Mon, 15 Jan 2024 10:30:00 -0500
C: Message-ID: <unique-id@sender.example.org>
C: MIME-Version: 1.0
C: Content-Type: text/plain; charset=UTF-8
C:
C: Hi Bob,
C:
C: Let's meet tomorrow at 2pm.
C:
C: Thanks,
C: Alice
C: .
S: 250 2.0.0 Message accepted for delivery
<CRLF>.<CRLF> — a line containing only a periodThe Dot-Stuffing Mechanism:
Since a line containing only . signals the end of data, lines in the message body that naturally start with a period must be escaped. This is called "dot stuffing":
Original message line: .This line starts with a period
Transmitted as: ..This line starts with a period
Original message line: ...three periods
Transmitted as: ....three periods
The receiving server removes the extra leading period, restoring the original content.
Transparency Procedure:
# Sender: Before transmitting each line
if line.startswith('.'):
line = '.' + line
# Receiver: After receiving each line (except terminator)
if line.startswith('..'):
line = line[1:]
1234567891011121314151617181920
C: DATAS: 354 Start mail input; end with <CRLF>.<CRLF>C: From: alice@example.orgC: To: bob@example.comC: Subject: Punctuation testC:C: This message tests dot stuffing.C: C: ..Imagine this line started with just one period.C: C: In the sentence below, the period is safe:C: The quick brown fox. jumps over the lazy dog.C: C: But these lines in the original started with periods:C: ..start with periodC: ...dotted leader styleC: C: End of message.C: .S: 250 2.0.0 Message accepted for deliveryFor large messages, the BDAT command (CHUNKING extension) provides a more efficient alternative to DATA. It transmits message content in binary chunks with explicit byte counts, eliminating the need for dot-stuffing and CRLF-only termination.
Beyond the core mail transfer commands, SMTP includes commands for session management, debugging, and control flow. While some are rarely used in production, understanding them is essential for protocol completeness.
RSET — Reset Transaction State
The RSET command aborts the current mail transaction without closing the connection:
C: MAIL FROM:<alice@example.org>
S: 250 2.1.0 Sender OK
C: RCPT TO:<bob@example.com>
S: 250 2.1.5 Recipient OK
C: RSET
S: 250 2.0.0 OK
# Transaction cleared; can start fresh
C: MAIL FROM:<carol@example.org>
S: 250 2.1.0 Sender OK
Use Cases:
NOOP — No Operation
The NOOP command does nothing except elicit a success response. It's used to:
C: NOOP
S: 250 2.0.0 OK
QUIT — End Session
The QUIT command gracefully terminates the SMTP session:
C: QUIT
S: 221 2.0.0 Bye
# Connection closed
After QUIT, the server closes the connection. The client should also close its end. Failing to send QUIT before disconnecting is considered impolite but doesn't affect message delivery.
HELP — Request Help Information
The HELP command requests information about commands:
C: HELP
S: 214-Commands supported:
S: 214-HELO EHLO MAIL RCPT DATA RSET
S: 214-NOOP QUIT VRFY HELP
S: 214 For more info use HELP <topic>
C: HELP MAIL
S: 214 MAIL FROM:<sender> [SP <parameters>]
Note: Many servers disable or limit HELP output for security reasons.
| Command | Syntax | Purpose | Response |
|---|---|---|---|
| RSET | RSET | Abort current transaction, clear buffers | 250 OK |
| NOOP | NOOP | No action; keep connection alive | 250 OK |
| QUIT | QUIT | End session gracefully | 221 Bye |
| HELP | HELP [topic] | Request command help | 214 (multiline) |
SMTP servers typically close idle connections after 5-10 minutes. If you're holding a connection for future sends, use NOOP periodically to prevent timeout. However, opening fresh connections for each transaction is often more reliable than connection pooling.
SMTP includes commands for verifying addresses and expanding mailing lists. However, these are rarely enabled in production due to security and privacy concerns.
VRFY — Verify Address
The VRFY command asks the server if a mailbox exists:
C: VRFY bob
S: 250 Bob Smith <bob@example.com>
C: VRFY unknown
S: 550 5.1.1 User unknown
C: VRFY amb
S: 553 Ambiguous; try BOB or AMBER
Why VRFY is Dangerous:
Modern Response:
Most servers either:
502 VRFY command disabled252 Cannot VRFY user, but will accept message550 VRFY not supportedEXPN — Expand Mailing List
The EXPN command expands a mailing list alias to its member addresses:
C: EXPN engineering
S: 250-Alice Smith <alice@example.com>
S: 250-Bob Jones <bob@example.com>
S: 250 Carol Chen <carol@example.com>
C: EXPN unknown-list
S: 550 No such list
Why EXPN is Even More Dangerous:
Modern Status:
EXPN is almost universally disabled. RFC 5321 explicitly notes that servers may refuse to implement these commands.
Rather than VRFY, most servers validate recipients during RCPT TO processing. This is safer because the sender is already committed to a transaction. Some servers even defer validation until after DATA, returning bounces later to prevent information leakage.
SMTP is a stateful protocol with a defined sequence of commands. Commands issued out of sequence trigger a 503 Bad sequence of commands error. Understanding this state machine is essential for correct client implementation.
SMTP State Machine:
| Current State | Valid Commands | Invalid Commands (503 Error) |
|---|---|---|
| After connection | EHLO, HELO, QUIT, NOOP | MAIL, RCPT, DATA |
| After EHLO/HELO | MAIL FROM, STARTTLS, AUTH, RSET, QUIT, NOOP | RCPT TO, DATA |
| After MAIL FROM | RCPT TO, RSET, QUIT, NOOP | MAIL FROM, DATA |
| After RCPT TO (at least one) | RCPT TO, DATA, RSET, QUIT, NOOP | MAIL FROM |
| After DATA (in body) | Message content only | All SMTP commands |
| After . (message end) | MAIL FROM, RSET, QUIT, NOOP | RCPT TO, DATA |
Multiple Messages Per Session:
SMTP allows sending multiple messages over a single TCP connection. After a successful DATA transaction, the client can immediately begin a new transaction with MAIL FROM:
C: EHLO client.example.org
S: 250-server.example.com
S: 250 OK
# First message
C: MAIL FROM:<alice@example.org>
S: 250 OK
C: RCPT TO:<bob@example.com>
S: 250 OK
C: DATA
S: 354 Start input
C: [message content]
C: .
S: 250 OK Message 1 accepted
# Second message (same session)
C: MAIL FROM:<alice@example.org>
S: 250 OK
C: RCPT TO:<carol@example.com>
S: 250 OK
C: DATA
S: 354 Start input
C: [message content]
C: .
S: 250 OK Message 2 accepted
C: QUIT
S: 221 Bye
If anything goes wrong during a transaction (rejected recipient, client-side error), use RSET to return to a clean state. This is more efficient than closing and reopening the connection. RSET always returns the session to the state immediately after EHLO.
Traditional SMTP requires the client to wait for a response after each command before sending the next. On high-latency networks, this creates significant delays. The PIPELINING extension (RFC 2920) allows batching multiple commands without waiting for individual responses.
Without Pipelining (Traditional):
C: MAIL FROM:<alice@example.org>
[wait ~100ms for response]
S: 250 OK
C: RCPT TO:<bob@example.com>
[wait ~100ms for response]
S: 250 OK
C: RCPT TO:<carol@example.com>
[wait ~100ms for response]
S: 250 OK
C: DATA
[wait ~100ms for response]
S: 354 Start input
# Total overhead: ~400ms of round-trip latency
With Pipelining:
C: MAIL FROM:<alice@example.org>
RCPT TO:<bob@example.com>
RCPT TO:<carol@example.com>
DATA
[single wait for all responses]
S: 250 OK
S: 250 OK
S: 250 OK
S: 354 Start input
# Total overhead: ~100ms (one round-trip)
Pipelining Rules:
PIPELINING in EHLO responseEHLO/HELO — Must wait for response to learn capabilitiesSTARTTLS — Must wait for TLS negotiationDATA (before body) — Must wait for 354 response before sending bodyAUTH — Must wait for authentication challenges/responses1234567891011121314151617181920212223242526272829303132
# Step 1: Non-pipelined EHLO (must wait for capabilities)C: EHLO client.example.orgS: 250-server.example.comS: 250-PIPELININGS: 250-8BITMIMES: 250 SIZE 52428800 # Step 2: Pipelined envelope (all sent together)C: MAIL FROM:<alice@example.org>RCPT TO:<bob@example.com>RCPT TO:<carol@example.com>RCPT TO:<invalid@example.com>DATA # Step 3: Server responds to all pipelined commandsS: 250 2.1.0 Sender OKS: 250 2.1.5 Recipient OKS: 250 2.1.5 Recipient OKS: 550 5.1.1 User unknownS: 354 Start mail input # Despite one rejected recipient, DATA was accepted # because at least one recipient was valid # Step 4: Message body (cannot be pipelined with other commands)C: From: alice@example.orgC: To: bob@example.com, carol@example.comC: Subject: Pipelined messageC:C: This message was sent using SMTP pipelining.C: .S: 250 2.0.0 Message acceptedWhen pipelining, a client must still process all responses in order. If a MAIL FROM fails, subsequent RCPT TO commands will also fail with 503. Robust clients should handle these cascading failures gracefully and use RSET to recover.
We've comprehensively explored the SMTP command vocabulary—the building blocks of every email transaction. Let's consolidate the essential knowledge:
. must be escaped; a lone . signals end of message| Command | Purpose | Typical Response |
|---|---|---|
| EHLO | Extended greeting, capability negotiation | 250 (multiline with extensions) |
| HELO | Basic greeting (legacy) | 250 |
| MAIL FROM | Specify sender (return path) | 250 |
| RCPT TO | Specify recipient (may repeat) | 250 or 550 |
| DATA | Begin message transfer | 354, then 250 after . |
| RSET | Abort transaction, reset state | 250 |
| NOOP | Keepalive, no action | 250 |
| QUIT | End session | 221 |
| VRFY | Verify address (usually disabled) | 250 or 550 |
| EXPN | Expand mailing list (usually disabled) | 250 or 550 |
| HELP | Request help text | 214 |
What's Next:
With command knowledge in hand, we're ready to explore Mail Submission—how email clients authenticate with and submit messages to their organization's mail servers, including the authentication mechanisms and security requirements that protect against unauthorized email sending.
You now possess a comprehensive understanding of SMTP commands—their syntax, semantics, sequencing, and responses. This knowledge enables you to debug email issues, implement SMTP clients, and understand the protocol exchanges underlying every email sent on the Internet.