Skip to content

Feedback loops and ARF: turning complaints into suppressions

Mailbox providers report spam complaints as ARF messages. Parse them, map them back to the recipient and suppress automatically within minutes.

Koltrix Team4 min read
A pile of opened paper envelopes
Photo by sue hughes on Unsplash
On this page(8 sections)
  1. What a feedback loop is
  2. Anatomy of an ARF report
  3. Mapping a complaint back to a recipient
  4. Building the pipeline
  5. 1. Receive
  6. 2. Parse
  7. 3. Identify
  8. 4. Suppress
  9. 5. Measure
  10. Speed matters
  11. Common mistakes
  12. Checklist
  13. Bottom line

Every spam complaint is a recipient telling you, in the bluntest way available, to stop. Feedback loops turn that click into a message your systems can read.

Whether you act on it in minutes or let it pile up in a mailbox decides whether the next complaint happens.

What a feedback loop is

When a user clicks "Report spam" or "Junk" in their mailbox, some providers send a report back to the sender. This arrangement is called a feedback loop (FBL). Providers that offer them generally require you to register the IPs or domains you send from and give an address to receive reports.

The reports follow a common format: ARF, the Abuse Reporting Format, defined in RFC 5965. Because most FBLs use ARF, one parser handles reports from many sources.

Not every provider offers a traditional FBL. Gmail, for example, does not send per-message complaint reports to senders. Instead it provides aggregate spam rates in Postmaster Tools, and supports a Feedback-ID header that lets large senders see complaint rates broken down by campaign identifiers they choose. So your complaint pipeline needs two inputs: ARF reports where available, and aggregate rates where not.

Anatomy of an ARF report

An ARF report is a MIME message with content type multipart/report; report-type=feedback-report, containing three parts:

  1. A human-readable explanation in text/plain.
  2. A machine-readable section of type message/feedback-report, with fields describing the complaint.
  3. The original message, or its headers, as message/rfc822 or text/rfc822-headers.

The machine-readable part looks something like this:

Feedback-Type: abuse
User-Agent: ExampleFBL/1.0
Version: 1
Original-Mail-From: <[email protected]>
Arrival-Date: Tue, 15 Sep 2026 14:02:11 +0000
Reported-Domain: example.com
Source-IP: 192.0.2.25

Key fields:

Field Meaning
Feedback-Type Usually abuse for spam complaints; RFC 5965 also defines fraud, virus and other
Version Always 1
Original-Mail-From The envelope sender of the reported message
Arrival-Date When the provider received it
Source-IP The IP that sent it
Original-Rcpt-To The recipient, when the provider includes it

Many providers redact the recipient address for privacy, which is why you cannot rely on Original-Rcpt-To being present.

Mapping a complaint back to a recipient

If the recipient is redacted, you need another way to identify them. Common techniques:

  • VERP-style envelope senders. Encode an opaque recipient token in the bounce address, such as [email protected]. The Original-Mail-From field then identifies the recipient directly, and the same address also maps bounces.
  • A custom header. Add something like X-Recipient-Token: 8f3a1c to every message. Providers that include the original headers pass it back.
  • Tokens in links. Unsubscribe or tracking URLs carry an opaque identifier, which may survive in the attached original.

Use opaque, random tokens rather than encoding the email address. A token you can look up in your database is safer, and it avoids leaking addresses if the report is forwarded somewhere unexpected.

Building the pipeline

A complaint pipeline does five things.

1. Receive

Point every FBL registration at a dedicated address on a subdomain you control, such as [email protected], and route that address to a program rather than a human mailbox. An inbound webhook from your mail service, or a small SMTP listener, both work.

2. Parse

Extract the message/feedback-report part and the original message part. In Python, the standard email package handles the MIME structure:

import email
from email import policy

def parse_arf(raw: bytes):
    msg = email.message_from_bytes(raw, policy=policy.default)
    if msg.get_content_type() != "multipart/report":
        return None, None
    report = original = None
    for part in msg.iter_parts():
        ctype = part.get_content_type()
        if ctype == "message/feedback-report":
            # The parser exposes the "Field: value" block as a nested message.
            payload = part.get_payload()
            report = payload[0] if isinstance(payload, list) else email.message_from_string(payload)
        elif ctype == "message/rfc822":
            original = part.get_payload()[0]
        elif ctype == "text/rfc822-headers":
            original = email.message_from_string(part.get_content())
    return report, original

Real reports vary, so test against samples from each provider you register with.

3. Identify

Find the recipient token from the envelope sender, a custom header or a link in the original. Also capture the campaign or message type, if you include it in a header.

4. Suppress

Suppress the recipient immediately for the mail stream they complained about. For marketing mail, that usually means unsubscribing them from all marketing. Whether a complaint about a newsletter should also stop account notifications is a product decision; most teams keep essential transactional mail flowing, since a password reset is not something the user meant to block.

5. Measure

Record each complaint against the campaign, segment and list source. Aggregate complaint rates per day and per stream, and alert when they rise. Gmail and Yahoo both tell bulk senders to keep spam rates below 0.3%, with Gmail recommending under 0.1%, so a rate trending toward those figures is an incident.

Speed matters

The goal is to suppress before you send that person anything else. If you run a daily newsletter and process complaints weekly, one complainer can receive and report six more messages. Aim to process reports within minutes of arrival, and make sure suppression is checked at send time, not only when a campaign is built.

Common mistakes

  • Sending FBL reports to a person's inbox. Nobody reads them consistently.
  • Ignoring reports with redacted recipients. Embed a token so they remain usable.
  • Suppressing too broadly or too narrowly. Decide per stream, document the rule, and apply it consistently.
  • Counting complaints without context. A complaint count means little without the matching delivered volume.
  • Forgetting providers without FBLs. Watch Gmail's aggregate spam rate alongside your ARF data.

Checklist

  • Register for feedback loops with providers that offer them.
  • Route reports to an automated endpoint on a dedicated subdomain.
  • Parse ARF and extract the original message.
  • Embed an opaque recipient token in envelope senders or headers.
  • Suppress within minutes and check suppression at send time.
  • Track complaint rate per stream against delivered volume, with alerts.

Bottom line

Feedback loops give you an itemized list of people who no longer want your mail. ARF makes the reports machine-readable, opaque tokens make them traceable even when recipients are redacted, and an automated pipeline turns them into suppressions before the next send. Pair that with aggregate spam rates from providers without FBLs and you will catch complaint problems while they are still small.

Start with Koltrix

Your domain, one inbox, and an API that sends.

A team inbox where AI sorts and drafts (nothing is sent without your click), plus the transactional API and SMTP relay your product sends with. 7 days free, no card.

SharePost on XLinkedIn