A bounce is an answer. Handle it once, automatically.
A bounce is the receiving mail server telling you it will not deliver a message. Some of those answers are final and some are not, and the whole of bounce handling is telling the two apart, then never sending to a finally-rejected address again. Notix does the second part for you and sends your code the first part as an email.bounced webhook.
Hard bounces and soft bounces.
When a sending server hands a message to a receiving server, the receiver answers with a three-digit SMTP reply. A reply starting with 2 accepts the message. A reply starting with 5 is a permanent refusal: the address does not exist, the domain has no mail server, the receiver will not take mail from this sender. That is a hard bounce. A reply starting with 4 is a temporary refusal: the mailbox is full, the message is too large, the server is busy or is deliberately delaying unknown senders for a few minutes. That is a soft bounce. The sending server keeps retrying a 4xx for a while, usually several hours, before it gives up and reports a bounce.
The distinction matters because the right response differs. A hard bounce means the address is wrong and sending again only damages your reputation with the receiver. A soft bounce means nothing is wrong with the address, and the next message may well get through.
| Kind | SMTP reply | Common reasons | What Notix does |
|---|---|---|---|
| Hard bounce | 5xx permanent failure, for example 550 5.1.1 | The address does not exist, the domain has no mail server, the receiver rejected the sender outright. | The address goes on the suppression list with reason HARD_BOUNCE. Later sends to it are not attempted. |
| Soft bounce | 4xx transient failure, for example 452 4.2.2 | Mailbox full, message too large, the receiving server is busy or greylisting, a temporary DNS problem. | Reported as a bounce of type Transient. The address is not suppressed; the next send is attempted normally. |
| Undetermined | A reply the receiver did not classify | A non-standard rejection that could not be read as permanent or transient. | Reported with type Undetermined. Not suppressed; treat a repeat as a hard bounce in your own logic. |
What happens to the address automatically.
When a bounce comes back as permanent, Notix adds each recipient that actually bounced to your team’s suppression list with the reason HARD_BOUNCE and a pointer to the email that caused it. Only the recipients named in the bounce are added: if a message went to three people and one address was dead, the other two are untouched. Transient and undetermined bounces are reported but never suppress anything.
From then on a send to that address is refused before it reaches the queue. The API still accepts the request and returns an email id, but the message is recorded with the status SUPPRESSED, nothing goes out, and the send does not count against your allowance. Where a message has several recipients and only some are suppressed, those are dropped and the rest are delivered. The same list is checked for transactional mail and for campaigns and journeys, so an address that bounced on a receipt is not tried again by a newsletter next week. The suppression list page covers the other two reasons an address lands there.
What email.bounced delivers.
Every bounce, permanent or not, is posted to each endpoint subscribed to the event. The envelope is the same for every Notix event; the bounce object is what this one adds.
{
"id": "call_01j9x2n4",
"type": "email.bounced",
"version": "2026-01-18",
"createdAt": "2026-09-13T10:30:12.000Z",
"teamId": 123,
"attempt": 1,
"data": {
"id": "email_01j9x2mz",
"status": "BOUNCED",
"from": "receipts@acme.com",
"to": ["someone@example.com"],
"subject": "Your receipt for order 4821",
"occurredAt": "2026-09-13T10:30:11Z",
"campaignId": null,
"contactId": null,
"domainId": 42,
"metadata": { "orderId": "4821" },
"bounce": {
"type": "Permanent",
"subType": "NoEmail",
"message": "550 5.1.1 The email account that you tried to reach does not exist."
}
}
}
data.id is the email you sent, so you can join it to whatever you stored when you called the API; metadata is returned exactly as you passed it. bounce.type is Permanent, Transient or Undetermined. bounce.subType narrows it: NoEmail for an address that does not exist, MailboxFull, MessageTooLarge, ContentRejected, AttachmentRejected, Suppressed or OnAccountSuppressionList when the underlying transport refused the address from its own history, and General otherwise. bounce.message carries the receiver’s reply text when there was one.
Each call is signed. The X-Notix-Signature header is an HMAC-SHA256 of the X-Notix-Timestamp value, a dot and the raw body, keyed with the endpoint’s secret; X-Notix-Event names the event and X-Notix-Retry is set on a retry. If your endpoint does not answer 2xx the call is retried with exponential backoff up to six attempts. The webhooks guide has the verification code for each SDK and the manual HMAC check.
// app/api/notix/route.ts
import { Notix } from "notix-js";
import { markEmailInvalid } from "@/lib/users";
const webhooks = new Notix(process.env.NOTIX_API_KEY!).webhooks(process.env.NOTIX_WEBHOOK_SECRET!);
export async function POST(request: Request) {
const rawBody = await request.text();
const event = webhooks.constructEvent(rawBody, { headers: request.headers }); // throws on a bad signature
if (event.type === "email.bounced" && event.data.bounce.type === "Permanent") {
for (const address of event.data.to) {
await markEmailInvalid(address, event.data.bounce.subType);
}
}
return new Response("ok");
}
What to do in your product.
Notix stops the sending. Your product still has a user whose address does not work, and only your product can fix that. On a permanent bounce, mark the address invalid on the account and show it the next time the user signs in: a banner asking for a working address, with the old one prefilled so a typo is easy to spot. Do not retry the send; the suppression list will refuse it, and a receipt that was not delivered is better replaced by an in-app copy than by a second bounce. If the address was for a one-time code or a password reset, the sign-in flow should offer another channel rather than another email.
On a transient bounce, do nothing visible. The receiving server has already spent hours retrying before reporting it, and most of these resolve on the next send. Log it, and if the same address is transient three times in a row treat it as invalid in your own data. Undetermined bounces are rare; handle them like transient ones and watch for repeats.
Bounce rate and reputation.
Receivers count how much of your mail they refuse. A domain that keeps sending to dead addresses looks like a domain that bought or scraped its list, and the receiver’s response is to slow down or spam-folder the mail it would otherwise have accepted. Automatic suppression is the single largest lever here, because it turns every bounce into exactly one bounce. The rest is not sending to addresses nobody typed in: verify a signup address with a code before it goes on a list, and treat an imported list as unverified until it has been sent to once with the bounces removed. How receivers weigh bounces alongside complaints and authentication is on the deliverability page.
Questions, answered.
What is the difference between a hard bounce and a soft bounce?
How do I handle email bounces automatically?
What happens if I send to an address that already hard-bounced?
Does a soft bounce ever become a hard bounce?
Can I remove an address from the suppression list?
What bounce rate is acceptable?
Keep reading.
Email suppression lists
Why a suppression list exists, the three reasons an address lands on it, and why one list across transactional and marketing matters.
LearnEmail deliverability and sender reputation
What mailbox providers score, the complaint threshold that matters, warming a new domain, and the honest position on shared IPs.
ProductTransactional email API
OTPs, receipts and alerts with idempotency keys, a deliverability pre-check and a shared suppression list.
ProductEmail API
One JSON API for transactional and marketing email: send, batch, schedule, webhooks, typed SDKs.
NotixDocs
The quickstart: verify a domain, copy an API key, send the first email.
Bounces handled before you write a line.
Suppression is on for every team, and the bounce webhook is a subscription away.