This document specifies the Sendmail Milter protocol, version 6, integrating the version 2 base protocol with subsequent extensions.
All integer types are transmitted in network byte order (big-endian). Data is byte-aligned.
uint32
: 32-bit unsigned integer.uint16
: 16-bit unsigned integer.char
: 8-bit character/byte.char name[]
: A NUL-terminated string.char buf[len]
: A fixed-length character array oflen
bytes, not NUL-terminated.char args[][]
: An array of NUL-terminated strings, concatenated one after another, filling the data portion of the packet.
Communication occurs over a stream-based socket (TCP or Unix). Data is sent in packets with the following structure:
uint32 len
: Total length of the packet's payload (cmd + data).char cmd
: A single-character command or response code.char data[len-1]
: Code-specific data.
The MTA initiates and drives the conversation. The filter MUST only send a response when one is expected for a given command.
The first command sent by the MTA after connection is SMFIC_OPTNEG
. The filter must respond with its own SMFIC_OPTNEG
packet to establish the parameters for the session.
MTA to Filter Packet ('O'):
uint32 version
: Protocol version offered by MTA (e.g., 6).uint32 actions
: Bitmask of actions the MTA supports (SMFIF_*
flags).uint32 protocol
: Bitmask of protocol steps the MTA can provide (SMFIP_*
flags).
Filter to MTA Packet ('O'):
uint32 version
: Protocol version selected by filter (must be <= MTA version).uint32 actions
: Bitmask of actions the filter may perform. This MUST be a subset of the actions offered by the MTA.uint32 protocol
: Bitmask of protocol steps the filter wishes to skip.
Set by the filter to declare which modification actions it may use.
Flag | Value | Action |
---|---|---|
ADDHDRS | 0x000001 |
Add or insert headers (SMFIR_ADDHEADER , SMFIR_INSHEADER ). |
CHGBODY | 0x000002 |
Replace the message body (SMFIR_REPLBODY ). |
ADDRCPT | 0x000004 |
Add a recipient (SMFIR_ADDRCPT ). |
DELRCPT | 0x000008 |
Delete a recipient (SMFIR_DELRCPT ). |
CHGHDRS | 0x000010 |
Change or delete a header (SMFIR_CHGHEADER ). |
QUARANTINE | 0x000020 |
Quarantine the message (SMFIR_QUARANTINE ). |
CHGFROM | 0x000040 |
Change the envelope sender (SMFIR_CHGFROM ). |
ADDRCPT_PAR | 0x000080 |
Add a recipient with ESMTP parameters (SMFIR_ADDRCPT_PAR ). |
SETSYMLIST | 0x000100 |
Declare which macros the filter requires (SMFIR_SETSYMLIST ). |
Set by the filter to request that the MTA skip certain commands or alter its behavior.
Flag | Value | Behavior |
---|---|---|
Skip Commands | ||
NOCONNECT | 0x000001 |
Skip SMFIC_CONNECT . |
NOHELO | 0x000002 |
Skip SMFIC_HELO . |
NOMAIL | 0x000004 |
Skip SMFIC_MAIL . |
NORCPT | 0x000008 |
Skip SMFIC_RCPT . |
NOBODY | 0x000010 |
Skip all SMFIC_BODY chunks. |
NOHDRS | 0x000020 |
Skip all SMFIC_HEADER commands. |
NOEOH | 0x000040 |
Skip SMFIC_EOH . |
NOUNKNOWN | 0x000100 |
Do not send unknown SMTP commands (SMFIC_UNKNOWN ). |
NODATA | 0x000200 |
Do not send SMFIC_DATA . |
Suppress Replies | ||
NR_CONN | 0x00010000 |
Do not expect a reply for SMFIC_CONNECT . |
NR_HELO | 0x00020000 |
Do not expect a reply for SMFIC_HELO . |
NR_MAIL | 0x00040000 |
Do not expect a reply for SMFIC_MAIL . |
NR_RCPT | 0x00080000 |
Do not expect a reply for SMFIC_RCPT . |
NR_DATA | 0x00100000 |
Do not expect a reply for SMFIC_DATA . |
NR_UNKN | 0x00200000 |
Do not expect a reply for SMFIC_UNKNOWN . |
NR_HDR | 0x00000080 |
Do not expect a reply for SMFIC_HEADER . |
NR_EOH | 0x00400000 |
Do not expect a reply for SMFIC_EOH . |
NR_BODY | 0x00800000 |
Do not expect a reply for SMFIC_BODY . |
Other Behavior | ||
SKIP | 0x00000400 |
Filter can use SMFIR_SKIP to stop receiving body chunks early. |
RCPT_REJ | 0x00000800 |
Send SMFIC_RCPT for recipients that were already rejected by the MTA. |
HDR_LEADSPC | 0x01000000 |
Send header values with leading space intact. MTA will not auto-add spaces. |
MDS_256K | 0x10000000 |
Max DATA size is 256K (semantics unconfirmed). |
MDS_1M | 0x20000000 |
Max DATA size is 1M (semantics unconfirmed). |
Code | Name | Data Structure | Response Expected? |
---|---|---|---|
'A' | ABORT | (none) | No |
'B' | BODY | char buf[] (up to 65535 bytes of a body chunk) |
Yes |
'C' | CONNECT | char hostname[] , char family , [uint16 port] , char address[] |
Yes |
'D' | MACRO | char cmdcode , char nameval[][] (pairs of macro name and value) |
No |
'E' | BODYEOB | (none) End of Body. | Yes (Mod + A/R) |
'H' | HELO | char helo[] (HELO/EHLO string) |
Yes |
'L' | HEADER | char name[] , char value[] |
Yes |
'M' | char args[][] (MAIL FROM address and ESMTP args) |
Yes | |
'N' | EOH | (none) End of Headers. | Yes |
'O' | OPTNEG | uint32 version , uint32 actions , uint32 protocol |
Yes (SMFIC_OPTNEG ) |
'Q' | QUIT | (none) | No (Close conn) |
'R' | RCPT | char args[][] (RCPT TO address and ESMTP args) |
Yes |
'T' | DATA | (none) Sent upon receiving the DATA command. | Yes |
'U' | UNKNOWN | char command[] (The unknown SMTP command string) |
Yes |
Sent in response to commands that expect a reply. A conversation ends with one of these.
Code | Name | Data Structure | Description |
---|---|---|---|
'a' | ACCEPT | (none) | Accept message and stop processing this filter. |
'c' | CONTINUE | (none) | Accept this stage and continue processing. |
'd' | DISCARD | (none) | Silently discard the message. |
'r' | REJECT | (none) | Reject with a permanent (5xx) error. |
't' | TEMPFAIL | (none) | Reject with a temporary (4xx) error. |
'y' | REPLYCODE | char smtpcode[3] , char ' ' , char text[] |
Reject with a specific [45]xx code and message. |
's' | SKIP | (none) | Stop receiving SMFIC_BODY chunks. Proceed to SMFIC_BODYEOB . |
Sent in response to SMFIC_BODYEOB
, just before a final accept/reject action.
Code | Name | Data Structure | Description |
---|---|---|---|
'+' | ADDRCPT | char rcpt[] |
Add a recipient to the envelope. |
'-' | DELRCPT | char rcpt[] |
Remove a recipient from the envelope. |
'2' | ADDRCPT_PAR | char args[][] (recipient and ESMTP args) |
Add a recipient with ESMTP parameters. |
'b' | REPLBODY | char buf[] |
Replace a chunk of the message body. |
'e' | CHGFROM | char args[][] (sender and ESMTP args) |
Change the envelope sender address. |
'h' | ADDHEADER | char name[] , char value[] |
Add a header to the end of the header block. |
'i' | INSHEADER | uint32 index , char name[] , char value[] |
Insert a header at the specified index. |
'm' | CHGHEADER | uint32 index , char name[] , char value[] |
Change header at index . Empty value deletes it. |
'q' | QUARANTINE | char reason[] |
Quarantine the message with a given reason. |
Code | Name | Data Structure | Description |
---|---|---|---|
'p' | PROGRESS | (none) | Sent asynchronously to reset MTA timeout during a long operation. |
'l' | SETSYMLIST | char cmdcode , char macros[][] |
Declare required macros for a given stage. (Semantics not fully specified). |
A single milter connection can handle multiple email transactions. The state resets after each completed message.
- Connection: The MTA establishes a stream connection to the filter.
- Negotiation: The MTA sends a single
SMFIC_OPTNEG
('O') command, declaring its capabilities (version, supported actions, available protocol steps). - Filter Response: The filter MUST reply with its own
SMFIC_OPTNEG
('O') packet, selecting a compatible version and declaring the actions it will perform and the protocol steps it wishes to skip. This negotiation governs the entire life of the connection.
For each new SMTP session, the following sequence occurs, unless skipped by negotiation (SMFIP_NOCONNECT
, SMFIP_NOHELO
).
- (Optional) Connection Info: The MTA sends
SMFIC_MACRO
('D') for the connect stage, followed bySMFIC_CONNECT
('C'). The filter replies with an Accept/Reject (A/R) action. A reject terminates the SMTP session. - (Optional) HELO/EHLO: The MTA sends
SMFIC_MACRO
('D') for the HELO stage, followed bySMFIC_HELO
('H'). The filter replies with an A/R action.
For each message within the SMTP session, the following sequence occurs, unless stages are skipped by negotiation.
- Mail From: The MTA sends
SMFIC_MACRO
('D') for the mail stage, followed bySMFIC_MAIL
('M'). The filter replies with an A/R action. - RCPT To: For each recipient, the MTA sends
SMFIC_MACRO
('D') for the recipient stage, followed bySMFIC_RCPT
('R'). The filter replies with an A/R action for each recipient. - Headers: For each header, the MTA sends
SMFIC_HEADER
('L'). The filter replies with an A/R action (unlessSMFIP_NR_HDR
is set). - End of Headers: The MTA sends
SMFIC_EOH
('N'). The filter replies with an A/R action. - Body: The MTA sends the message body in one or more
SMFIC_BODY
('B') chunks. The filter replies with an A/R action for each chunk (unlessSMFIP_NR_BODY
is set).- The filter may respond with
SMFIR_SKIP
('s') at any point during this stage to request that the MTA stop sending body chunks and proceed immediately to the End of Body stage.
- The filter may respond with
- End of Body: The MTA sends
SMFIC_BODYEOB
('E'). - Filter Response: The filter MUST reply with a sequence of packets:
a. Zero or more Modification Action packets (e.g.,
SMFIR_ADDHEADER
,SMFIR_CHGFROM
,SMFIR_REPLBODY
). b. Exactly one final Accept/Reject Action packet (e.g.,SMFIR_ACCEPT
,SMFIR_REJECT
,SMFIR_CONTINUE
).
SMFIR_ACCEPT
/SMFIR_CONTINUE
: After a message is accepted, the filter's state resets to the post-HELO
stage, ready to process a newSMFIC_MAIL
command on the same connection.SMFIC_ABORT
('A'): The MTA signals an unexpected end to the current message transaction (e.g., client RSET). The filter MUST discard state for the current message and reset to the post-HELO
stage. No response is sent.SMFIC_QUIT
('Q'): The MTA signals the end of the SMTP session. The filter MUST close the connection. No response is sent.
SMFIC_MACRO
('D') packets provide context for the SMTP command that immediately follows them.- The
cmdcode
field in the packet indicates which command the macros apply to (e.g., 'C' forCONNECT
, 'M' forMAIL
). - Filters SHOULD be prepared to receive a
SMFIC_MACRO
packet before any command that can have associated macros.
SMFIR_CHGHEADER
('m'): Theindex
is 1-based and scoped per header name. Anindex
of 2 forname
"Received" targets the second "Received" header. A zero-length string forvalue
(a single NUL byte) deletes the specified header.SMFIR_INSHEADER
('i'): Theindex
is 1-based and specifies the absolute position in the header list where the new header will be inserted. Anindex
of 1 inserts at the top.
- Use of
SMFIR_REPLBODY
('b') implies replacement of the entire message body. The original body is discarded. - The filter must supply a new, complete body. This new body can be split across multiple
SMFIR_REPLBODY
packets. - The sequence of
SMFIR_REPLBODY
packets is sent during theSMFIC_BODYEOB
stage and is terminated by a final Accept/Reject action.
- The filter can send a
SMFIR_PROGRESS
('p') packet at any time it is performing a long-running task to prevent the MTA from timing out. - The MTA will consume the progress packet and reset its response timer, continuing to wait for the definitive response.
-
SMFIC_DATA
('T'): Sent by the MTA upon receiving the client'sDATA
command, occurring after allRCPT
commands but before anyHEADER
commands. This provides an early stage to reject a message before header or body content is transmitted. -
SMFIC_UNKNOWN
('U'): The MTA forwards an SMTP command string that it does not recognize. This allows the filter to implement custom or non-standard commands. The filter can respond with any A/R action. -
SMFIR_CONN_FAIL
('f'): This response instructs the MTA to terminate the client SMTP session immediately, typically with a 421 "Service not available" error. It differs fromREJECT
orTEMPFAIL
, which apply to a specific SMTP command, whereasCONN_FAIL
applies to the entire connection. The specification for this response is incomplete. -
SMFIC_QUIC_NC
('K'): "QUIT but new connection follows". This is an advisory command from the MTA indicating it is closing the current milter connection but intends to establish a new one immediately. It is an optimization hint. The filter should treat it identically toSMFIC_QUIT
('Q'). The specification for this command is incomplete. -
SMFIR_SETSYMLIST
('l'): Used by the filter to declare the specific macros it requires for each protocol stage. This is a response, typically sent once afterOPTNEG
, to reduce unnecessary data transmission from the MTA. The specification for its usage is incomplete.