How to reduce your email bounce rate
Most of your hard bounces are visible before you press send; the rest belong to the receiving server, and no validator can see them for you.
Contents
If you want to reduce your email bounce rate, the useful first move is to split "bounce" into two problems, because they have two different fixes. Most advice blurs them together, promises you a magic number, and skips the part where it admits what it cannot do. This post does the opposite: it shows you the bounces you can remove before you send, and it is honest about the ones that only the receiving mail server knows about.
Hard bounces and soft bounces are not the same problem
A bounce is the receiving server declining your message. There are two flavours, and they call for opposite responses.
A soft bounce is temporary. The mailbox is full, the server is busy, the message is too large. Retry later and it may go through.
A hard bounce is permanent. The domain does not exist, there is no mail server behind it, or the address is garbage. Retrying does nothing except make you look careless.
That last part is the whole reason to care. Mailbox providers watch your hard-bounce rate as a proxy for how well you manage your list. A steady drip of permanent failures reads like you are mailing addresses you never checked, and your sender reputation — the thing that decides whether the good addresses even reach the inbox — pays for it. Soft bounces are noise. Hard bounces are the ones that compound.
The hard bounces you can see coming
Some hard bounces are unavoidable: an address that was real yesterday and got deleted this morning is invisible to everyone until you mail it. But a meaningful share of them are sitting in your list right now, visible before you press send:
- Malformed syntax —
jane@@example, a trailing comma, a fat-thumbed space. It was never a valid address. - A domain with no MX record — the domain resolves, but nothing behind it accepts mail. Every message to it hard-bounces.
- A disposable domain — a throwaway inbox the signup used once and abandoned. It technically accepts mail, but it is dead weight and a spam-trap risk.
These are the avoidable hard bounces. You do not need anyone's permission or a paid provider to catch the first category — your language already does. The point is to catch all three before the send, not after the bounce report.
Validate before you send
The pattern is a gate: check each address, and only queue the ones that clear syntax, have a mail server, and are not disposable. Here is that gate against Boundstone's email endpoint. A single request returns the three signals you need.
import requests
API = "https://api.boundstone.io/v1/verify/email"
HEADERS = {"Authorization": "Bearer bs_live_YOUR_KEY"}
def should_send(email: str) -> bool:
r = requests.post(API, json={"email": email}, headers=HEADERS, timeout=10)
r.raise_for_status()
data = r.json()
# The avoidable hard bounces: bad syntax, no mail server, throwaway domain.
if not data["valid_syntax"]:
return False
if not data["mx_found"]:
return False
if data["disposable"]:
return False
return True
for address in ["jane@example.com", "not-an-email", "user@mailinator.com"]:
print(address, "->", "send" if should_send(address) else "skip")
A valid, deliverable-looking address comes back like this:
{
"valid_syntax": true,
"domain": "example.com",
"mx_found": true,
"disposable": false,
"role_account": true,
"free_provider": false,
"checks": {
"performed": ["syntax", "mx", "disposable_list", "role_list"],
"not_performed": ["smtp_mailbox", "catch_all"]
}
}
role_account and free_provider are there as judgement calls, not hard filters. A role address like support@ or sales@ is a mailing-list decision, not a bounce risk, so this gate leaves it in. Note that free_provider is reported but is deliberately not listed under checks.performed — it is a hint about the address, not a deliverability verdict.
What validation cannot promise — and why that is the useful part
Look again at checks.not_performed: ["smtp_mailbox", "catch_all"]. This is not fine print. It is the reason you can trust the valid_syntax and mx_found above it.
Boundstone does not perform SMTP mailbox verification, and it does not perform catch-all detection. That means validation cannot prove a specific mailbox exists, and it cannot guarantee your message lands. mx_found: true tells you the domain is ready to receive mail; it does not tell you jane is a real account behind it. Only the receiving server knows that, and it will only tell you when you actually send — which is the moment the bounce happens.
So be precise about the win. Validating syntax, MX, and disposable status removes the hard bounces you can see coming. It does not remove the ones only the receiving server can see. Any tool that claims otherwise is either running live SMTP probes (which have their own reputation cost) or lying to you. Boundstone would rather show you the empty column than pretend it is full. If you want the longer version of that distinction, see email validation vs verification.
Where the standard library stops and the API starts
Reach for the free option first, because it is often all you need. Python's email.utils.parseaddr, a decent regex, or a library like email-validator will catch malformed syntax for zero dollars and zero network calls. If your list problem is mostly typos and paste errors, stop there — you have handled the cheap, common majority for free.
The API earns its place at the next layer: live MX resolution, a maintained disposable-domain list, and one consistent response contract across every language your stack speaks, so the gate above looks the same in Python, Node, or Go. If you would rather see it than read about it, the keyless email validator runs the exact same checks in the browser with no signup, and there is a full validate-email-in-Python walkthrough if that is your stack.
The short version
- Split your bounces. Soft bounces are temporary and mostly harmless; hard bounces damage sender reputation.
- Kill the avoidable hard bounces before you send: bad syntax, no MX record, disposable domain. Those three checks are one request.
- Start with your standard library. It catches malformed addresses for free.
- Use the API for the layer past that: live MX, disposable lists, one contract across clients.
- Believe the honest limit. Validation cannot prove a mailbox exists or guarantee delivery —
smtp_mailboxandcatch_allarenot_performed, and that admission is exactly why thevalid_syntaxandmx_foundnext to it mean something.