| Internet-Draft | Milter Protocol | October 2026 |
| Adams & Kucherawy | Expires 29 April 2027 | [Page] |
This document specifies the "milter" protocol, a binary request/response protocol by which a Mail Transfer Agent (MTA) can delegate inspection and modification of an SMTP transaction to one or more external filtering processes.¶
This note is to be removed before publishing as an RFC.¶
This document describes existing widely deployed practice originating in the sendmail "libmilter" implementation. Prose sections marked [[TODO]] are placeholders.¶
Publication is being sought to archive the protocol which has never been publicly documented other than in its code form. The protocol has not evolved substantially since the release of version 6, and prior versions are considered historical.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 29 April 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Milter (a portmanteau of "mail filter") was officially introduced into open source Sendmail 8.12.0 released on September 8, 2001. It provided a plugin architecture by which a Mail Transfer Agent (MTA) can communicate with an arbitrary number of local filtering agents to observe and possibly modify messages in transit or influence their delivery. Filters implementing the milter protocol (called "milters") are optionally involved at each phase of an SMTP [RFC5321] transaction.¶
Milter is also supported by Postfix, which together with Sendmail provides milter capabilities to the bulk of open source MTA deployment worldwide.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
Numeric constants are given in hexadecimal with a "0x" prefix. Command and response identifiers are single US-ASCII characters and are shown in single quotes; the quotes are not part of the protocol.¶
The milter protocol is a connection-oriented, binary, largely half-duplex request/response protocol. The MTA connects to the filter and drives the exchange; the filter reacts. There is no filter-initiated request other than responses.¶
The protocol runs over any reliable, ordered, octet-stream transport. In practice this is TCP [RFC9293] over IPv4 or IPv6, or a local (AF_UNIX) stream socket. No port number is reserved; the port or socket path is a matter of local configuration. The protocol provides no authentication, integrity, or confidentiality of its own (see Section 11).¶
One milter connection carries the negotiation phase followed by zero or more SMTP connection contexts, each carrying zero or more message transactions:¶
An abort resets to the start of a message transaction while preserving the SMTP connection context. A quit ends the milter connection; a "quit but new connection follows" resets to the start of an SMTP connection context while preserving negotiated options.¶
MTA Filter |--- 'O' OPTNEG ----------------------->| |<-- 'O' OPTNEG (agreed) --------------| |--- 'D' MACRO(for 'C') --------------->| (optional, no reply) |--- 'C' CONNECT ---------------------->| |<-- 'c' CONTINUE ---------------------| |--- 'H' HELO ------------------------->| |<-- 'c' CONTINUE ---------------------| |--- 'M' MAIL ------------------------->| |<-- 'c' CONTINUE ---------------------| |--- 'R' RCPT ------------------------->| (once per recipient) |<-- 'c' CONTINUE ---------------------| |--- 'T' DATA ------------------------->| |<-- 'c' CONTINUE ---------------------| |--- 'L' HEADER ----------------------->| (once per header field) |<-- 'c' CONTINUE ---------------------| |--- 'N' EOH -------------------------->| |<-- 'c' CONTINUE ---------------------| |--- 'B' BODY ------------------------->| (repeated) |<-- 'c' CONTINUE or 'S' SKIP -------| |--- 'E' BODYEOB ---------------------->| |<-- zero or more modification actions -| |<-- final disposition ----------------| |--- 'A' ABORT / 'Q' QUIT / 'K' QUIT_NC >|¶
All protocol data units ("packets") share one framing:¶
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | len (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | cmd/resp | | +-+-+-+-+-+-+-+-+ data (len - 1 octets) + | ... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+¶
A receiver MUST impose an upper bound on len and MUST close the connection rather than attempt to buffer an implausibly large packet. Implementations commonly bound the negotiated maximum body chunk (Section 5.3) and reject packets exceeding it. A receiver MUST treat an unknown identifier, a truncated packet, a missing NUL terminator, or a data length inconsistent with the identifier's definition as a protocol error and close the connection.¶
Multiple packets MAY be coalesced into a single transport write; receivers MUST NOT rely on message boundaries in the transport stream.¶
Immediately after the connection is established, the MTA MUST send SMFIC_OPTNEG ('O') and the filter MUST reply with SMFIC_OPTNEG ('O'). No other command may precede this exchange.¶
'O' version (uint32) protocol version, 6 for this specification
actions (uint32) bitmask of SMFIF_* (Section 5.2)
protocol (uint32) bitmask of SMFIP_* (Section 5.3)
¶
In the MTA's offer, "actions" and "protocol" advertise everything the MTA is able to support. In the filter's reply, the filter MUST NOT set any bit the MTA did not offer; the reply bits are the operative agreement for the remainder of the connection. The filter MUST request in "actions" every modification it may later attempt, and MUST set in "protocol" the bits describing the commands it wishes to be spared and the replies it will omit. An MTA that receives a version it does not support, or a bit it did not offer, MUST close the connection.¶
The filter's reply MAY be followed by zero or more macro list entries, permitted only if SMFIF_SETSYMLIST was negotiated:¶
stage (uint32) 0=connect 1=helo 2=mail 3=rcpt
4=data 5=eom 6=eoh
macros (string) space-separated macro names, NUL-terminated
¶
Each entry replaces the MTA's default macro set for that stage. A stage MUST NOT appear more than once.¶
Version 6 is specified by this document and is the only version implementations are expected to support. Versions 2 and 4 exist in historical deployments; they lack, among other things, the DATA and UNKNOWN commands, recipient-parameter and sender-change actions, the SKIP response, and the no-reply protocol bits. Implementations SHOULD offer version 6 and MAY accept earlier versions for interoperability.¶
| Value | Name | Permits |
|---|---|---|
| 0x00000001 | SMFIF_ADDHDRS | Add header fields ('h') |
| 0x00000002 | SMFIF_CHGBODY | Replace the body ('b') |
| 0x00000004 | SMFIF_ADDRCPT | Add a recipient ('+') |
| 0x00000008 | SMFIF_DELRCPT | Delete a recipient ('-') |
| 0x00000010 | SMFIF_CHGHDRS | Change or delete header fields ('m'), insert ('i') |
| 0x00000020 | SMFIF_QUARANTINE | Quarantine the message ('q') |
| 0x00000040 | SMFIF_CHGFROM | Change the envelope sender ('e') |
| 0x00000080 | SMFIF_ADDRCPT_PAR | Add a recipient with ESMTP parameters ('2') |
| 0x00000100 | SMFIF_SETSYMLIST | Send macro lists during negotiation |
Bits in the first group suppress a command entirely: the MTA will not send it and the filter will not reply to it. Bits in the second group ("no reply") keep the command but declare that the filter will send no response packet for it; the MTA MUST proceed as if SMFIR_CONTINUE had been received. Filters MUST NOT send a response for a stage whose no-reply bit was negotiated.¶
| Value | Name | Meaning |
|---|---|---|
| 0x00000001 | SMFIP_NOCONNECT | Do not send 'C' |
| 0x00000002 | SMFIP_NOHELO | Do not send 'H' |
| 0x00000004 | SMFIP_NOMAIL | Do not send 'M' |
| 0x00000008 | SMFIP_NORCPT | Do not send 'R' |
| 0x00000010 | SMFIP_NOBODY | Do not send 'B' |
| 0x00000020 | SMFIP_NOHDRS | Do not send 'L' |
| 0x00000040 | SMFIP_NOEOH | Do not send 'N' |
| 0x00000080 | SMFIP_NR_HDR | No reply for 'L' |
| 0x00000100 | SMFIP_NOUNKNOWN | Do not send 'U' |
| 0x00000200 | SMFIP_NODATA | Do not send 'T' |
| 0x00000400 | SMFIP_SKIP | Filter may send 'S' (SKIP) |
| 0x00000800 | SMFIP_RCPT_REJ | Also report recipients the MTA rejected |
| 0x00001000 | SMFIP_NR_CONN | No reply for 'C' |
| 0x00002000 | SMFIP_NR_HELO | No reply for 'H' |
| 0x00004000 | SMFIP_NR_MAIL | No reply for 'M' |
| 0x00008000 | SMFIP_NR_RCPT | No reply for 'R' |
| 0x00010000 | SMFIP_NR_DATA | No reply for 'T' |
| 0x00020000 | SMFIP_NR_UNKN | No reply for 'U' |
| 0x00040000 | SMFIP_NR_EOH | No reply for 'N' |
| 0x00080000 | SMFIP_NR_BODY | No reply for 'B' |
| 0x00100000 | SMFIP_HDR_LEADSPC | Header values retain leading space; no space is added on output |
| 0x10000000 | SMFIP_MDS_256K | Maximum body chunk 262144 octets |
| 0x20000000 | SMFIP_MDS_1M | Maximum body chunk 1048576 octets |
Absent an MDS bit, the maximum body chunk is 65535 octets. If both MDS bits are offered the filter MUST select at most one. There is no no-reply bit for end of message ('E'); a filter MUST always answer 'E'.¶
Unless a no-reply or suppression bit was negotiated, the MTA MUST wait for a response to each command before sending the next, and the filter MUST send exactly one terminal response per command.¶
| Code | Name | Stage |
|---|---|---|
| 'O' (0x4F) | SMFIC_OPTNEG | Negotiation |
| 'D' (0x44) | SMFIC_MACRO | Any (no reply) |
| 'C' (0x43) | SMFIC_CONNECT | Connection |
| 'H' (0x48) | SMFIC_HELO | Connection |
| 'M' (0x4D) | SMFIC_MAIL | Envelope |
| 'R' (0x52) | SMFIC_RCPT | Envelope |
| 'T' (0x54) | SMFIC_DATA | Envelope |
| 'L' (0x4C) | SMFIC_HEADER | Message |
| 'N' (0x4E) | SMFIC_EOH | Message |
| 'B' (0x42) | SMFIC_BODY | Message |
| 'E' (0x45) | SMFIC_BODYEOB | End of message |
| 'U' (0x55) | SMFIC_UNKNOWN | Any |
| 'A' (0x41) | SMFIC_ABORT | Any (no reply) |
| 'Q' (0x51) | SMFIC_QUIT | Any (no reply) |
| 'K' (0x4B) | SMFIC_QUIT_NC | Any (no reply) |
'D' cmdcode (char) command letter these macros apply to
pairs ... name NUL value NUL, repeated to end of packet
¶
Conveys named symbols ("macros") describing the state of the SMTP session. It is not a command in its own right: it carries no reply, and it MUST immediately precede the command identified by cmdcode. Macro values persist for the scope of the stage in which they were sent and are discarded when that scope ends. Names are conveyed without the leading '$' or enclosing braces used in MTA configuration syntax. See Section 8.¶
'C' hostname (string) validated client name, or bracketed address
family (char) 'U' unknown, 'L' unix, '4' inet, '6' inet6
port (uint16) client source port, present unless family 'U'
address (string) textual address, present unless family 'U'
¶
Sent once per SMTP connection, before any other transaction command. When family is 'U', port and address are absent. For 'L', port is present but meaningless and address is the socket path. Address literals follow [RFC5321] textual form.¶
'H' helo (string) argument of the HELO or EHLO command¶
May occur more than once per SMTP connection. A second HELO/EHLO resets the SMTP transaction but not the milter connection context.¶
'M' args ... one or more NUL-terminated strings¶
The first string is the envelope sender in angle-bracketed [RFC5321] form; the remainder, if any, are the ESMTP parameters of the MAIL command, one per string. This command begins a message transaction.¶
'R' args ... one or more NUL-terminated strings¶
As for 'M', but the first string is one envelope recipient. Sent once per recipient. A rejection response applies only to that recipient and does not terminate the transaction. If SMFIP_RCPT_REJ was negotiated, recipients the MTA itself rejected are also reported, distinguishable through macros.¶
'T' (no data)¶
Signals the SMTP DATA command, after the last recipient and before any header. A rejection here rejects the whole message.¶
'L' name (string) header field name, without the colon
value (string) header field value
¶
Sent once per header field, in the order the fields appear. Folded fields are delivered with their embedded CRLF sequences intact and are not unfolded. Unless SMFIP_HDR_LEADSPC was negotiated, the whitespace separating the colon from the value is removed from value, and the MTA reinserts a single space when emitting header fields supplied by the filter.¶
'N' (no data)¶
Marks the end of the header block.¶
'B' chunk ... opaque body octets, not NUL-terminated¶
Sent zero or more times. Each chunk carries at most the negotiated maximum (Section 5.3) minus framing overhead. Chunk boundaries are arbitrary and carry no semantics: they need not align with line boundaries, and a filter MUST NOT assume otherwise. Line endings are CRLF as received on the wire. Dot-stuffing as defined in [RFC5321] is removed by the MTA before the body is presented.¶
'E' (no data)¶
Marks the end of the message body. This is the only stage at which the filter may send modification actions (Section 7.5). The filter MUST conclude with exactly one terminal response.¶
'U' command (string) the unrecognized SMTP command text¶
Reports an SMTP command the MTA did not recognize or could not process. It does not change the transaction state.¶
'A' (no data)¶
Abandons the current message transaction. The filter MUST NOT reply and MUST discard all state accumulated since 'M', retaining connection and HELO state. It may arrive at any point in a transaction, including after 'E'.¶
'Q' (no data) close the milter connection 'K' (no data) close the SMTP connection context; reuse this one¶
Neither is answered. After 'Q' the filter MUST release all state and close the connection. After 'K' the filter MUST discard transaction and connection state but retain the negotiated options and await a new 'C' on the same milter connection.¶
Responses fall into three classes: terminal dispositions, which conclude the handling of a command; non-terminal responses, which do not conclude it; and modification actions, valid only at end of message and always followed by a terminal disposition.¶
| Code | Name | Class |
|---|---|---|
| 'c' (0x63) | SMFIR_CONTINUE | Terminal |
| 'a' (0x61) | SMFIR_ACCEPT | Terminal |
| 'r' (0x72) | SMFIR_REJECT | Terminal |
| 'd' (0x64) | SMFIR_DISCARD | Terminal |
| 't' (0x74) | SMFIR_TEMPFAIL | Terminal |
| 'y' (0x79) | SMFIR_REPLYCODE | Terminal |
| 'f' (0x66) | SMFIR_SHUTDOWN | Terminal |
| 'p' (0x70) | SMFIR_PROGRESS | Non-terminal |
| 'S' (0x53) | SMFIR_SKIP | Terminal (body only) |
| 'O' (0x4F) | SMFIC_OPTNEG | Negotiation only |
| '+' (0x2B) | SMFIR_ADDRCPT | Modification |
| '2' (0x32) | SMFIR_ADDRCPT_PAR | Modification |
| '-' (0x2D) | SMFIR_DELRCPT | Modification |
| 'e' (0x65) | SMFIR_CHGFROM | Modification |
| 'h' (0x68) | SMFIR_ADDHEADER | Modification |
| 'i' (0x69) | SMFIR_INSHEADER | Modification |
| 'm' (0x6D) | SMFIR_CHGHEADER | Modification |
| 'b' (0x62) | SMFIR_REPLBODY | Modification |
| 'q' (0x71) | SMFIR_QUARANTINE | Modification |
The effect of a disposition sent before the message transaction begins — that is, at connect or HELO — applies to the SMTP connection rather than to a single message.¶
'y' reply (string) "NNN[ ENH.S.C] text", NUL-terminated¶
Rejects with a filter-supplied SMTP reply. The first three octets MUST be a three-digit reply code beginning with '4' or '5' as defined in [RFC5321], optionally followed by a space, an enhanced status code [RFC3463], and free text. Whether the reply is treated as temporary or permanent follows its leading digit. The text MUST NOT contain bare CR or LF; a multiline reply is expressed by embedding CRLF-separated lines formatted per [RFC5321], and MTAs MAY refuse such replies. A "%" in the text is interpreted by some MTAs as introducing a format directive and MUST therefore be escaped as "%%".¶
'p' (no data)¶
Indicates that the filter is still working and requests that the MTA reset its response timer. It does not conclude the command; the filter MUST still send a terminal response. It MAY be sent repeatedly and is intended chiefly for long-running end-of-message processing.¶
'S' (no data)¶
Valid only in response to SMFIC_BODY and only if SMFIP_SKIP was negotiated. The filter has seen as much of the body as it needs; the MTA MUST stop sending 'B' packets for this message and proceed to 'E'. It is equivalent to SMFIR_CONTINUE with respect to disposition.¶
Modification actions MUST be sent only in response to SMFIC_BODYEOB, MUST be permitted by the action flags agreed during negotiation, and MUST be followed by exactly one terminal disposition. Any number may be sent, and their effects are applied by the MTA in the order received. A filter MUST NOT expect the MTA to report the outcome of an individual action; the MTA MAY reject the whole connection if an action is malformed or unnegotiated.¶
'h' name (string) append a header field
value (string)
---
'i' index (uint32) insert a header field; 1 = before the first
name (string) existing field, 0 is treated as 1; an index
value (string) past the end appends
---
'm' index (uint32) 1-based occurrence of this field name to
name (string) change; empty value deletes the field
value (string)
---
'b' data ... replacement body octets, not NUL-terminated
---
'+' rcpt (string) add envelope recipient
---
'2' rcpt (string) add envelope recipient with ESMTP parameters
args (string)
---
'-' rcpt (string) delete envelope recipient
---
'e' from (string) replace envelope sender
args (string) optional ESMTP parameters; may be absent
---
'q' reason (string) quarantine; empty reason releases
¶
Notes on individual actions:¶
Macros are name/value pairs supplied through SMFIC_MACRO (Section 6) and give the filter access to MTA state not otherwise carried on the wire. Availability is MTA-specific and, when SMFIF_SETSYMLIST is negotiated, configurable per stage. A filter MUST tolerate the absence of any macro. The following are widely implemented.¶
| Name | Earliest stage | Value |
|---|---|---|
| j | connect | Canonical name of the MTA host |
| _ | connect | Validated client name and address |
| {daemon_name} | connect | Name of the MTA daemon instance |
| {daemon_port} | connect | Local port the client connected to |
| {if_name}, {if_addr} | connect | Local interface name and address |
| {client_addr}, {client_port} | connect | Client address and source port |
| {client_name} | connect | Client PTR name, if verified |
| {client_ptr} | connect | Result of the reverse lookup |
| {client_connections} | connect | Concurrent connections from this client |
| {tls_version}, {cipher}, {cipher_bits} | helo | TLS session parameters |
| {cert_subject}, {cert_issuer} | helo | Client certificate identity |
| {verify} | helo | Certificate verification result |
| {auth_type}, {auth_authen} | SASL mechanism and authenticated identity | |
| {auth_author} | Authorization identity, if asserted | |
| {auth_ssf} | SASL security strength factor | |
| {mail_addr}, {mail_host}, {mail_mailer} | Resolved envelope sender | |
| i | MTA queue identifier for this message | |
| {rcpt_addr}, {rcpt_host}, {rcpt_mailer} | rcpt | Resolved current recipient |
| {rcpt_reject} | rcpt | Set when the MTA itself rejected this recipient |
The protocol carries no error response. Any framing violation, unexpected or out-of-sequence identifier, or action that was not negotiated is a protocol error, and the detecting party MUST close the connection without further exchange.¶
An MTA MUST apply a timeout to each response and SHOULD apply separate timeouts to connection establishment, to sending, and to end-of-message processing. SMFIR_PROGRESS restarts the response timer. On timeout, or on any failure to connect to or communicate with a filter, the MTA MUST apply a locally configured default action; a temporary rejection is RECOMMENDED as it is fail-closed without permanently losing mail.¶
Where an MTA consults several filters, it does so in a configured order, passing each stage through the filters in turn; the first non-continue disposition terminates consideration of that stage, and modifications made by one filter are visible to those following it.¶
This document specifies no actions for IANA.¶
The protocol provides no authentication, integrity protection, or confidentiality. A milter connection can carry the full content of every message, including credentials asserted through macros, in cleartext, and a filter can reject, discard, or rewrite any message. Deployments MUST therefore confine milter traffic to a trusted path — a local socket with restrictive permissions, a loopback interface, or a channel protected by TLS [RFC8446] or an equivalent — and MTAs MUST NOT accept filter connections from, nor connect to filters at, unauthenticated network locations.¶
Further considerations: length fields are attacker-influenced and invite memory exhaustion if not bounded; a slow or wedged filter is a denial-of-service vector against the MTA, and the fail-open default action is a filtering-bypass vector; header and body modifications can invalidate signatures such as DKIM [RFC6376]; filter-supplied reply text is echoed to untrusted clients and can leak internal detail or be used for injection if not sanitized.¶
[[TODO]]¶