# Feed Mailgun events into Spaw's suppression list

Add your Spaw feedback URL as a Mailgun webhook for delivered, permanent failure and complaint events, and your suppression list keeps itself current.

Updated: 2026-09-03

Mailgun tells you about every message it could not deliver, and Spaw can listen. Register your Spaw feedback URL as a webhook on the sending domain and each permanent failure adds the address to your suppression list, each delivery confirms a mailbox for your own later lookups, and the History tab measures the verdicts against what actually happened.

## Set up the webhooks

1. Open the API keys page in the Spaw dashboard, generate a feedback key, and copy the Mailgun URL:

```text
https://spaw.co/api/v1/email/feedback/mailgun/fb_…
```

2. In Mailgun, open the sending domain and go to its Webhooks settings.
3. Add the URL for the Delivered Messages, Permanent Failure and Spam Complaints events. Mailgun lets each event type post to more than one URL, so this sits alongside anything you already have.
4. Use Mailgun's test button for each event. Spaw answers `200` with the number of outcomes it recorded; a test that carries no recipient records nothing and still answers `200`.

Temporary failures are not needed. Mailgun keeps retrying those on its own, and a temporary failure says nothing definitive about the address, so Spaw ignores the event even if you register it.

## What Spaw reads from the payload

Mailgun posts one event per request under `event-data`. Spaw reads the `event` name, the `recipient`, the `severity`, the delivery status, and the timestamp, and nothing else:

| Mailgun event | Recorded as |
| --- | --- |
| `delivered` | delivered |
| `failed` with `severity` permanent | bounced |
| `failed` with `severity` temporary | ignored |
| `complained` | complained |

The reason is taken from the first non-empty of `delivery-status.description`, `delivery-status.message` and `reason`, because Mailgun fills whichever it has. The Unix `timestamp` becomes the time of the outcome. A permanent failure, trimmed to the fields Spaw uses:

```json
{
  "event-data": {
    "event": "failed",
    "severity": "permanent",
    "recipient": "mia@acme.com",
    "reason": "bounce",
    "delivery-status": { "message": "550 5.1.1 The email account that you tried to reach does not exist" },
    "timestamp": 1788782400
  }
}
```

## What Spaw does with the events

A permanent failure or a complaint adds the address to your suppression list with source `feedback`. Batch, bulk and monitor runs answer it from the stored verdict at no cost from then on, with reason `suppressed` and a risk score of 100. A single lookup always re-verifies, which is the only way an address earns its way off the list.

A delivery removes any entry Spaw had added automatically, and for 90 days the address counts as a confirmed mailbox for your own lookups: the verification answers `smtp_reason: "delivered_recently"` and skips the mailbox probe. Every outcome is matched against the verdict you were given for the address in the previous 90 days, and the feedback summary reports the delivery rate among addresses answered `deliverable` and the bounce rate among addresses answered `undeliverable`. Outcomes are kept for 180 days.

If you already have a bounce export from before the webhook existed, import it once through the suppression import endpoint; imported entries are honoured from day one.

## Keep the URL private

Mailgun signs its webhooks, but Spaw does not need the signing key: the feedback key in the URL is the credential, and anyone holding the URL can add outcomes to your account. If it leaks, rotate the key on the API keys page; every old URL then answers `401 INVALID_FEEDBACK_KEY` and you paste the new one into Mailgun.

## Cost

Feedback is free and never logged as a lookup. The endpoint is throttled at 120 requests per minute per IP.

Reference: https://spaw.co/integrations/mailgun
