Verifying an email address at signup only proves it was valid at that moment. It says nothing about whether it's still valid six months later and for most contact databases, a meaningful share of it won't be. If your app verifies once at signup and never again, you're shipping a slow leak, not a fix.
This post covers why lists decay even without a single unsubscribe, what a re-verification job should actually check, and a basic cron-based implementation for keeping a contacts table clean over time.
Why valid-at-signup doesn't mean valid-forever
Email addresses have a shelf life, and email verification once at collection time only confirms the address was live on that specific day. Roughly 22.5% of email contacts become invalid over the course of a year — around 2.1% per month: driven by job changes, account deactivation, provider migrations, and abandoned inboxes. B2B contact lists decay faster than consumer lists, commonly in the 25–30% annual range, largely because employees change jobs and their work addresses get deactivated almost immediately.
If your database has contacts older than a few months and you've never re-verified, you're very likely sending to a nontrivial number of dead addresses without knowing it.
What causes Email list decay
Email list decay is driven by five recurring patterns, and only one of them is under your control.
- Job changes — the single biggest driver for B2B lists; an estimated 70% of B2B job-related email addresses change within 12 months of collection.
- Company changes — mergers, acquisitions, rebrands, or shutdowns can invalidate an entire domain's worth of addresses at once.
- Provider migrations — a contact moves from one email provider to another and abandons the old address.
- Account deactivation — mailbox providers periodically purge inactive accounts per their own retention policies.
- Signup typos — the one decay source that real-time syntax/MX verification at signup actually prevents.
The first four happen entirely outside your app, on a timeline you don't control, which is exactly why a one-time verification at signup can't be the whole strategy.
Email Spam traps: the risk re-verification catches that bounces don't
A recycled spam trap is a real address that mailbox providers have reclaimed after roughly 12 months of inactivity, then repurposed specifically to catch senders with stale lists. This is the mechanism that makes re-verification more than a bounce-rate optimization — it's a reputation risk mitigation.
There are three recognized spam trap types:
- Typo traps - created from signup typos (user@yahooo.com), catching senders who never validated syntax carefully.
- Recycled traps - formerly legitimate addresses that went dormant for a year or more and were reclaimed by the provider as a monitoring mechanism.
- Pristine traps - addresses that were never real signups at all, created solely to catch senders using purchased or scraped lists.
Recycled traps are the one your own signup-time verification can't catch, because the address was real and valid when the person signed up. It only becomes a trap after months of dormancy — which is precisely the gap a scheduled re-verification job is designed to close.
Designing the re-verification job
A re-verification job needs three things a one-off signup check doesn't:
A cadence - how often each contact gets re-checked (see the how-often section)
Prioritization - verifying your entire table every night at scale is wasteful; segment by last-engagement date or last-verified date and check the stalest/riskiest contacts first
A status write-back, not a delete — store the verification result (valid, invalid, catch-all, disposable, unknown) and last-checked timestamp rather than silently dropping rows, so your sending logic can make the suppress/send decision explicitly
A scheduled job example (Node.js + cron)
javascriptconst cron = require('node-cron');
const db = require('./db');
// Runs nightly at 2am — pulls contacts not verified in 90+ days,
// oldest-first, capped at a batch size to respect rate limits.
cron.schedule('0 2 * * *', async () => {
const staleContacts = await db.query(`
SELECT id, email FROM contacts
WHERE last_verified_at < NOW() - INTERVAL '90 days'
OR last_verified_at IS NULL
ORDER BY last_verified_at ASC NULLS FIRST
LIMIT 5000
`);
for (const contact of staleContacts) {
const result = await verifyEmail(contact.email); // your verification call
await db.query(`
UPDATE contacts
SET verification_status = $1, last_verified_at = NOW()
WHERE id = $2
`, [result.status, contact.id]);
if (result.status === 'invalid') {
await db.query(`UPDATE contacts SET suppressed = true WHERE id = $1`, [contact.id]);
}
}
});
The batch size and interval here are illustrative, tune them to your sending volume and your verification provider's rate limits. The important structural choice is the last_verified_at column: it turns re-verification into an ongoing, queryable process instead of a one-time event.
How often should you re-verify?
There's no universal answer, but the decay data gives a reasonable default: at roughly 2% monthly decay, a 90-day re-verification cycle catches most staleness before it compounds into a meaningful bounce or spam-trap risk, without re-checking addresses that haven't had time to go stale. High-value B2B lists with faster job-change turnover may justify a 30–60 day cycle; low-churn consumer lists can often stretch to 6 months.
If you're re-verifying manually or building this in-house, an email verification API that returns a distinct catch-all status (rather than lumping it in with valid) matters more here than at signup time, a domain's catch-all configuration can change between checks, and treating it as a hard valid inflates your list-quality metrics without actually reducing risk. MailValid supports exactly this: bulk re-verification with status codes designed for exactly this kind of scheduled job, plus a real-time API for the signup-time check.
Frequently Asked Questions
Does re-verification replace signup-time verification?
No, they catch different things. Signup-time verification (syntax + MX + SMTP) prevents typo'd and fake addresses from entering your database at all. Re-verification catches addresses that were genuinely valid at signup but have since gone stale.
Will re-verification hurt my sender reputation the way sending mail does?
No. SMTP-level verification checks mailbox existence via a RCPT TO command without completing delivery (no DATA command is sent), so it doesn't count as a send and doesn't itself generate spam complaints, provided it's run through a provider with dedicated verification IP infrastructure rather than your own sending domain.
Should I delete contacts that fail re-verification?
Suppressing them from sends is safer than deleting outright, a suppressed contact might resubscribe, correct a domain-level issue, or represent a data point you still want for reporting. Deletion is a business decision, not a deliverability requirement.
What if a contact I haven't emailed in a year suddenly becomes a spam trap?
This is exactly the recycled-trap scenario. If your last engagement with an address predates your last verification by many months, treat it as higher risk and prioritize it in your re-verification queue rather than assuming dormancy is harmless.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.