> ## Documentation Index
> Fetch the complete documentation index at: https://ahasend.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> AhaSend stores all message data in the EU. See /facts.
> The API base URL is https://api.ahasend.com. Send the API key in the Authorization: Bearer header. See /api-reference/authentication.
> Use send.ahasend.com for SMTP in the EU, or send-us.ahasend.com as a US connection point forwarding to the EU. Ports 25, 587 and 2525 use STARTTLS. Port 465 is not supported. See /smtp.
> For send-only integrations, create an API Key v2 under Credentials → Add and scope it to messages:send:{your-domain}. See /send-api/credentials and /api-reference/scopes.

# Email Bounces, Failures and Suppressions

> Handle AhaSend email bounces, temporary failures and suppressed recipients: map webhook events to delivery outcomes and check suppression expiry rules.

AhaSend reports bounces, failures and suppressed recipients through message logs and webhooks; this guide maps those outcomes to the action your application should take.

## What Is the Difference Between a Hard and Soft Bounce?

A hard bounce is a permanent rejection for that delivery attempt, such as an invalid mailbox. A soft bounce is a temporary delivery problem, such as a busy server. Read the server's full reply and the [SMTP error reference](/docs/smtp/errors), because the same broad code can have several causes.

| AhaSend event                                                                | What happened                                                                 | What your app should do                                                      |
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| [`message.reception`](/docs/api-reference/webhooks/message-reception)             | AhaSend accepted the message                                                  | Record acceptance; wait for the delivery outcome                             |
| [`message.delivered`](/docs/api-reference/webhooks/message-delivered)             | The recipient server accepted it                                              | Record delivery to the server, without assuming inbox placement              |
| [`message.transient_error`](/docs/api-reference/webhooks/message-transient_error) | An attempt hit a temporary problem                                            | Let AhaSend continue its message delivery attempts; do not send another copy |
| [`message.failed`](/docs/api-reference/webhooks/message-failed)                   | Delivery failed, including an exhausted delivery window                       | Inspect attempts, cause and expiry before deciding on another send           |
| [`message.bounced`](/docs/api-reference/webhooks/message-bounced)                 | The server rejected the message permanently, or a later bounce notice arrived | Stop retrying the same invalid address; inspect the reason                   |
| [`message.suppressed`](/docs/api-reference/webhooks/message-suppressed)           | A suppression prevented this send                                             | Look up the matching suppression and respect it                              |
| [`suppression.created`](/docs/api-reference/webhooks/suppression-created)         | An address was added to the suppression list                                  | Update your application's blocklist or recipient state                       |

The response to [Create Message](/docs/api-reference/messages/create-message) is an acceptance response with per-recipient `queued`, `scheduled` or `error` entries. It is not the final delivery state. Check each recipient even when the HTTP request succeeds.

## Why Was My Email Suppressed?

A suppression blocks sending to an address for the matching sending domain, or for the applicable account domains. Look up the address and sending domain with [List Suppressions](/docs/api-reference/suppressions/get-suppressions) or the [suppression CLI](/docs/cli/commands/suppressions). Read its reason and expiry instead of assuming every suppression came from a hard bounce.

AhaSend can create suppressions from delivery problems or spam complaints. Your application can also create one for an unsubscribe or an internal blocklist. Reasons are readable strings, including caller-supplied text; do not treat them as a fixed enum.

## Suppression Duration

| Cause                                   | Duration                                  |
| --------------------------------------- | ----------------------------------------- |
| Most bounces                            | 30 days                                   |
| Temporary delivery failures             | 7 days                                    |
| Message expires after repeated attempts | 15 days                                   |
| Spam complaint                          | Effectively permanent                     |
| Created through the API                 | Caller supplies the required `expires_at` |

API-created suppressions do not inherit a default expiry from their reason. Keep unsubscribes and complaints in your own durable recipient preferences too; an expiry must not silently restore permission to send unwanted mail.

## When Should I Remove a Suppression?

Remove a suppression only after resolving its cause and confirming that sending is appropriate. For an invalid mailbox, ask the user to correct or verify the address. A complaint or unsubscribe is not a reason to try a different sender domain. Use [Delete Suppression](/docs/api-reference/suppressions/delete-suppression) for the specific address and domain; clearing the whole list can restart unwanted mail.

## How Should My Webhook Handler Update State?

Verify the [signature](/docs/api-reference/webhooks/security), save the `webhook-id` delivery header to detect duplicate deliveries, and record the message ID, event type and timestamp. Acknowledge verified unknown event types with 2xx. Keep the event history so a late bounce or repeated notification does not erase useful earlier evidence.

A delivery attempt's `classification` is an open string. Values can change as classification improves; keep the original SMTP response and do not reject an entire webhook because a new classification appears. Distinguish [webhook delivery retries](/docs/api-reference/webhooks/retry-policy) from the email's own [schedule and expiry](/docs/send-api/send-email).

Test each branch with the [sandbox outcomes](/docs/send-api/sandbox) and [local webhook testing](/docs/cli/webhook-testing). Use the [bounce report API](/docs/api-reference/reports/get-bounce-report) to find patterns by recipient domain before changing volume.

## Related Guides

* [Domain setup](/docs/domains), [DNS providers](/docs/dns) and [send-only API keys](/docs/send-api/credentials).
* [REST API](/docs/send-api/send-email), [SMTP settings](/docs/smtp), [CLI quickstart](/docs/cli/quickstart), [Node.js SDK](/docs/guides/nodejs-sdk) and [Go SDK](/docs/guides/go-sdk).
* [Plans and limits](/docs/facts), [delivery webhooks](/docs/integrations/webhooks) and [troubleshooting](/docs/troubleshooting).
