The short answer: run the list through a batch email check, drop anything with the verdict refuse, fix the typos the check suggests, and send to role addresses (like info@) only if your message is meant for a team inbox.
Why it matters
Bounces and spam-trap hits hurt your sender reputation, and that decides whether your future emails reach the inbox at all. Old or imported lists collect dead domains, typos and throwaway addresses.
The script
import csv, os, requests
KEY = os.environ["VEQEN_API_KEY"]
with open("contacts.csv") as f:
emails = [row["email"] for row in csv.DictReader(f)]
keep, fixed, dropped = [], [], []
for start in range(0, len(emails), 100):
r = requests.post(
"https://veqen-app.com/v1/email/batch",
headers={"Authorization": f"Bearer {KEY}"},
json={"emails": emails[start : start + 100]},
timeout=60,
)
r.raise_for_status()
for item in r.json()["results"]:
if item.get("did_you_mean"):
fixed.append((item["email"], item["did_you_mean"]))
elif item["risk"]["verdict"] == "refuse":
dropped.append((item["email"], item["risk"]["reasons"][0]["code"]))
else:
keep.append(item.get("normalized") or item["email"])
print(f"keep {len(keep)}, typo fixes {len(fixed)}, dropped {len(dropped)}")
Cost: 1 credit per address. A 10,000-address list costs the Starter pack ($10).
Should you verify mailboxes too?
Add "verify_mailbox": true (+5 credits each) for lists of business addresses you haven't emailed in a long time. It asks each mail server whether the mailbox exists. It's less useful for Gmail, Outlook and Yahoo addresses, which don't reveal mailbox existence, and for catch-all domains, which accept everything.
What to do with each result
| Result | Action |
|---|---|
admit |
Keep. |
did_you_mean set |
Fix if you can confirm with the person; otherwise drop. |
role_account |
Keep for B2B messages to a company; drop for personal marketing. |
refuse |
Drop. |