A feedback loop (FBL) complaint fires when a recipient hits the 'Report Spam' button in their email client — Yahoo, Comcast, and other providers forward these reports to the sender via RFC5965 ARF-formatted emails. ESP platforms (SendGrid, SES, Mailgun) deliver FBL events directly via webhook. If the application does not process these events and add the complaining address to the suppression list immediately, the system will send again to someone who has explicitly marked the email as spam — guaranteeing a second complaint from the same address. Repeat complainers are among the strongest signals ISPs use to classify a sending IP as spam-originating.
High because CWE-345 (insufficient verification of data authenticity) applies to FBL processing — unverified or unprocessed complaint events allow repeat sends to explicit complainers, directly accumulating the complaint rates that trigger ISP filtering.
Add a complaint event handler to your ESP webhook processor. Every spamreport (SendGrid), complained (Mailgun), or SNS complaint notification (SES) must write to the suppression table immediately:
// In your ESP webhook handler (e.g., src/app/api/webhooks/sendgrid/route.ts)
export async function handleSendGridEvent(events: SendGridEvent[]): Promise<void> {
for (const event of events) {
if (event.event === 'spamreport') {
await db.suppression.upsert({
where: { email: event.email.toLowerCase() },
update: { reason: 'fbl_complaint', updatedAt: new Date() },
create: {
email: event.email.toLowerCase(),
reason: 'fbl_complaint',
source: 'sendgrid_fbl',
suppressedAt: new Date()
}
})
}
}
}
Verify webhook signatures before processing (SendGrid sends a X-Twilio-Email-Event-Webhook-Signature header). Persist FBL events to a complaintEvent table in addition to the suppression upsert so complaint rates can be computed per campaign and per domain.
ID: deliverability-engineering.bounce-fbl.fbl-processing
Severity: high
What to look for: Enumerate all FBL and complaint webhook handlers. Search for feedback loop (FBL) complaint handling. FBL complaints arrive when a recipient marks an email as spam. Look for: webhook endpoints consuming X-ARF reports, handlers for ESP complaint events (SendGrid has spamreport event type, SES sends complaint notifications via SNS, Mailgun sends complained events), or email inboxes being polled for ARF-formatted abuse reports. Check that complaint events trigger suppression of the complaining address.
Pass criteria: At least 1 FBL complaint handler receives events programmatically and the complaining address is immediately added to the suppression list. Ignoring FBL events does not count as pass.
Fail criteria: No FBL webhook or handler exists. Complaint events are not received programmatically — the team would only see complaint statistics in an ESP dashboard, with no suppression of complainers.
Skip (N/A) when: Sending volume is below 5,000/month and the ESP handles complaint suppression natively without application-level integration.
Detail on fail: "No FBL or spam complaint webhook handler found — complainers are not suppressed at the application level, risking repeat sends to hostile addresses" or "SendGrid spamreport event not handled in webhook processor"
Remediation: Handle complaint events from your ESP's webhook:
// SendGrid webhook handler (Express/Next.js API route)
export async function handleSendGridEvent(
events: SendGridEvent[]
): Promise<void> {
for (const event of events) {
if (event.event === 'spamreport') {
await db.suppression.upsert({
where: { email: event.email.toLowerCase() },
update: { reason: 'fbl_complaint', updatedAt: new Date() },
create: {
email: event.email.toLowerCase(),
reason: 'fbl_complaint',
source: 'sendgrid_webhook',
suppressedAt: new Date()
}
})
}
if (event.event === 'bounce' && event.type === 'bounce') {
await handleBounceEvent({ email: event.email, type: 'hard', bounceCode: event.status })
}
if (event.event === 'unsubscribe') {
await handleUnsubscribeEvent(event.email)
}
}
}