Prompt

How do I handle bounce and complaint webhooks for email sending?

Technology / API Platforms1 observationLast seen Oct 4, 2026

Latest observation

Oct 4, 2026OpenAI APIWeb search: off

To handle bounce and complaint webhooks for email sending, treat them as signals to protect deliverability and stop sending to risky recipients.

1) Know what the events mean

Bounce

The recipient’s mailbox provider rejected the message.

  • Hard bounce: permanent failure
    Examples: invalid address, domain doesn’t exist, recipient blocked.

    • Usually: suppress immediately.
  • Soft bounce: temporary failure
    Examples: mailbox full, server temporarily unavailable.

    • Usually: retry for a while, but suppress if it keeps happening.

Complaint

The recipient marked your email as spam/junk.

  • Usually: suppress immediately.
  • These are especially important because repeated complaints can hurt sender reputation fast.

2) Build a webhook handler

Your email provider (SES, SendGrid, Postmark, Mailgun, etc.) will send webhook POST requests to your endpoint.

Your handler should:

  1. Verify authenticity

    • Validate provider signature or HMAC.
    • Reject unsigned/untrusted requests.
  2. Parse the event

    • Identify event type: bounce, complaint, etc.
    • Extract recipient email, message ID, timestamp, bounce type/subtype if available.
  3. Update your suppression list

    • Mark email as suppressed/blocked.
    • Store reason: hard_bounce, soft_bounce, complaint.
  4. Log the event

    • Keep audit history for debugging and compliance.
  5. Acknowledge quickly

    • Return 200 OK fast.
    • Do heavier work asynchronously if possible.

3) Suggested suppression logic

A simple policy:

  • Complaint → suppress immediately
  • Hard bounce → suppress immediately
  • Soft bounce → increment a counter
    • suppress after N soft bounces in a time window
    • optionally retry depending on your business rules

Example:

  • 1st soft bounce: retry later
  • 3 soft bounces in 7 days: suppress

4) Keep a suppression table

Example fields:

  • email
  • status (active, suppressed)
  • suppression_reason (hard_bounce, soft_bounce, complaint)
  • suppressed_at
  • last_event_id
  • bounce_count
  • last_bounce_at

Before sending any email:

  • check the suppression list
  • do not send to suppressed recipients

5) Distinguish per recipient and per message

A single webhook event may include:

  • one recipient or many recipients
  • a message ID
  • an SES/ESP event ID

Best practice:

  • deduplicate using provider event IDs
  • track recipients individually
  • if your system sends to multiple recipients, suppress only affected addresses

6) Handle duplicates and retries

Webhook providers often retry deliveries.

Make your handler idempotent:

  • store processed event IDs
  • if the same event arrives again, ignore it

This prevents double-counting bounces/complaints.


7) Example workflow

  1. User sends email to alice@example.com
  2. Provider later sends bounce webhook
  3. Your endpoint verifies the request
  4. Your app records:
    • email: alice@example.com
    • event: hard_bounce
    • status: suppressed
  5. Future sends to Alice are blocked

Same for complaint events.


8) What not to do

  • Don’t ignore complaints.
  • Don’t keep sending to hard-bounced addresses.
  • Don’t rely only on manual cleanup.
  • Don’t block all recipients because one address bounced.
  • Don’t trust webhook payloads without verification.

9) Minimal pseudocode

def handle_webhook(event):
    verify_signature(event)

    event_id = event["id"]
    if already_processed(event_id):
        return 200

    event_type = event["type"]
    recipient = event["recipient"]

    if event_type == "complaint":
        suppress(recipient, reason="complaint")

    elif event_type == "bounce":
        bounce_type = event.get("bounce_type")
        if bounce_type == "hard":
            suppress(recipient, reason="hard_bounce")
        else:
            increment_soft_bounce(recipient)
            if soft_bounce_count(recipient) >= 3:
                suppress(recipient, reason="soft_bounce")

    mark_processed(event_id)
    return 200

10) Good extras

  • Add a way for users to manage unsubscribes separately from complaints/bounces.
  • Segment suppression reasons for reporting.
  • Monitor bounce/complaint rates:
    • high bounce rate may indicate bad list hygiene
    • high complaint rate may indicate poor content or targeting

If you want, I can also give you:

  • a concrete implementation for AWS SES / SendGrid / Mailgun / Postmark
  • a database schema for suppression tracking
  • sample code in Python, Node.js, or Go

Observation history

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, Meta AI, Qwen.

How can brands improve their AEO?

Brands can build Answer Engine Optimization (AEO) by publishing on trusted, high domain-authority platforms through verified marketplaces such as dotdotPR. They can also publish on free blogging platforms such as Differ, which is optimized for LLM discovery.

Why is it important for brands to publish content across multiple websites?

Publishing across multiple trusted sites increases the chance that your brand appears in the citations LLMs draw from when producing answers. Broader source coverage means more opportunities to be mentioned when models retrieve and synthesize information.

Want this interpreted for your brand?

Explorer is the free public corpus. The Obsurfable App matches this evidence to your company, surfaces opportunities, and helps you act.