Overview
Email bounces occur when a message cannot be delivered to the recipient. Hard bounces (permanent failures — invalid address, domain does not exist) must be removed from your sending list immediately to protect your sender reputation. Soft bounces (temporary failures — mailbox full, server unavailable) should be retried but tracked.
Most email service providers send bounce notification emails to a designated bounce address. By routing that bounce address through JsonHook, your application can process bounce events programmatically and update your contact database in real time — no manual list cleaning, no cron jobs polling an IMAP mailbox.
The typical flow is:
- Your ESP sends a bounce notification to
[email protected] - You forward that address to a JsonHook inbound address
- JsonHook delivers the parsed bounce email as JSON to your webhook
- Your handler extracts the bounced address and updates your database
Prerequisites
To automate bounce handling you need:
- A JsonHook inbound address pointed at a bounce-handling webhook endpoint
- Your ESP configured to send bounce notifications to that address (or a forwarding rule in your DNS/email provider)
- A database or contact list that stores email addresses and their deliverability status
- Understanding of the bounce email format your ESP uses — examine a few real bounce emails to identify where the bounced address appears
Automate Your Bounce Processing
Route bounce notifications through JsonHook. Keep your email list clean automatically.
Get Free API KeyStep-by-Step Instructions
Set up automated bounce handling:
- Create a JsonHook address for bounces:
POST /v1/addresses { "webhookUrl": "https://yourapp.com/webhooks/bounces", "label": "Bounce handler" } - Configure your ESP to send bounce notifications to your JsonHook address. In SendGrid, set the bounce notification address in Settings → Mail Settings → Bounce Purge. In Mailgun, set the bounce webhook or forward the default bounce address.
- Examine a test bounce payload. Send an email to a known invalid address and inspect the resulting bounce notification in your webhook payload. The bounced address is typically in the email body — look for patterns like "Original-Recipient:", "Final-Recipient:", or "failed permanently" followed by the address.
- Write extraction logic for the bounced address from
email.textBody. - Update your database — mark hard-bounced addresses as undeliverable and remove them from active sending lists.
- Differentiate hard vs soft bounces — look for "permanent" or "5xx" in the bounce message for hard bounces; "temporary" or "4xx" for soft bounces.
Code Example
A Node.js handler that parses bounce notifications and updates a contact database:
import express from "express";
import crypto from "crypto";
import { db } from "./db";
const app = express();
app.use(express.raw({ type: "application/json" }));
function extractBounceInfo(textBody: string) {
// RFC 3464 DSN format — most ESPs use this
const finalRecipient = textBody.match(
/Final-Recipient:s*rfc822;s*([^
]+)/i
)?.[1]?.trim();
const status = textBody.match(/Status:s*([d.]+)/i)?.[1];
const isHard = status?.startsWith("5") ?? false;
return { bouncedAddress: finalRecipient, isHard, status };
}
app.post("/webhooks/bounces", async (req, res) => {
const sig = req.headers["x-jsonhook-signature"] as string;
const expected = crypto
.createHmac("sha256", process.env.JSONHOOK_SECRET!)
.update(req.body).digest("hex");
if (sig !== expected) return res.sendStatus(401);
const { email } = JSON.parse(req.body.toString());
const { bouncedAddress, isHard, status } = extractBounceInfo(
email.textBody ?? ""
);
if (bouncedAddress) {
await db.contacts.update(
{ email: bouncedAddress },
{ bounceStatus: isHard ? "hard" : "soft", bounceCode: status }
);
if (isHard) {
await db.contacts.update({ email: bouncedAddress }, { active: false });
}
console.log(`Bounce: ${bouncedAddress} (${isHard ? "hard" : "soft"}, ${status})`);
}
res.sendStatus(200);
});
app.listen(3000);Common Pitfalls
Avoid these bounce-handling mistakes:
- Unsubscribing on soft bounces. Soft bounces (4xx status codes) are temporary — do not remove the address from your list on the first soft bounce. Apply a retry threshold (e.g., 3 consecutive soft bounces) before deactivating.
- Not handling DSN format variations. Different ESPs generate slightly different DSN (Delivery Status Notification) formats. Test with actual bounce emails from your specific ESP and write extraction logic accordingly.
- Missing the nested MIME structure. Bounce emails are often multipart with a machine-readable part (
message/delivery-status) and a human-readable explanation. The machine-readable part is the most reliable extraction target. - Acting on out-of-order bounces. A bounce notification may arrive after a contact has been re-verified. Check the bounce timestamp against your database before marking as inactive.
- Not logging unrecognized bounce formats. When extraction returns null, log the full
textBodyfor manual review rather than silently discarding the bounce.