Industry
Testable criteria for cold email software
Feature lists for cold email software are easy to read and hard to compare. This post reduces the choice to capabilities you can test yourself in an afternoon, each tied to a published standard or sender rule. It names no products, and every criterion has a pass condition you can observe.
Unsubscribe headers that pass a real check
RFC 8058 defines one-click unsubscribe. A message carries a List-Unsubscribe header with exactly one HTTPS URI and a List-Unsubscribe-Post header whose value is List-Unsubscribe=One-Click. The message must have a valid DKIM signature covering both headers, and the URI must not answer a POST with a redirect 1. The older header syntax, angle-bracketed URLs with mailto or http, comes from RFC 2369 2.
Google asks senders of 5,000 or more messages a day to support one-click unsubscribe on marketing mail 3. Yahoo adds that unsubscribe requests should be honored within 2 days 4. The test: send yourself a message from the tool, read the raw source, and confirm both headers are present and that the signature header lists them in its h= tag.
import { readFileSync } from 'node:fs';
const raw = readFileSync(process.argv.at(2), 'utf8');
const headerBlock = raw.split(/\r?\n\r?\n/).at(0) ?? '';
const unfolded = headerBlock.replace(/\r?\n[ \t]+/g, ' ');
const get = (name) =>
unfolded.split(/\r?\n/).find((line) => line.toLowerCase().startsWith(name + ':'));
const unsub = get('list-unsubscribe') ?? '';
const post = get('list-unsubscribe-post') ?? '';
const sig = get('dkim-signature') ?? '';
const signed = (sig.match(/\bh=([^;]*)/i)?.at(1) ?? '').toLowerCase();
const results = {
httpsUri: /<https:\/\/[^>]+>/i.test(unsub),
oneClickPost: /List-Unsubscribe=One-Click/.test(post),
signedUnsub: signed.includes('list-unsubscribe'),
signedPost: signed.includes('list-unsubscribe-post'),
oneFromHeader: unfolded.split(/\r?\n/).filter((l) => /^from:/i.test(l)).length === 1,
};
console.table(results);
process.exitCode = Object.values(results).every(Boolean) ? 0 : 1;
A header check you can run against a saved raw message
The last line of the script covers a related rule in RFC 5322: the From field appears exactly once 5.
The evaluation table
| Capability | How to test it | Pass condition |
|---|---|---|
| List validation hook | Import a file containing a null-MX domain, a disposable domain and a role mailbox | The tool can call an HTTP endpoint per address, or accept a result column, before sending |
| Per-domain throttling | Queue 200 messages to one recipient domain and watch the send log | You can set a cap per recipient domain and per sending domain, and the log shows it holding |
| Bounce handling | Send to an address you know is dead, then to a full mailbox | Permanent failures (5.x.x) suppress the address; temporary ones (4.x.x) retry 6 |
| Unsubscribe headers | Run the script above on a real message | All five checks print true |
| Suppression across campaigns | Unsubscribe in one campaign, then add the address to another | The second campaign skips it |
| Spam-rate visibility | Look for per-domain complaint data | You can see complaint rate against the 0.3 percent ceiling both Google and Yahoo publish 34 |
| Export and API access | Export contacts and events | Every field you need to rebuild suppression elsewhere comes out |
Capabilities to test, and what a pass looks like
Bounces, throttles and validation hooks
Enhanced status codes carry three numbers: a class, a subject and a detail. Class 4 is a persistent transient failure where a retry may work, class 5 is a permanent failure that resending will not fix, and 5.1.1 means the destination mailbox does not exist 6. RFC 5321 also says a relay that accepts a message and later finds it undeliverable must send a non-delivery notification to the originator, so a good tool parses those later notices and not only the immediate SMTP reply 7.
Per-domain throttling has no standard to cite, so test the behavior directly. The reason to want it is the complaint ceiling: a single noisy sending domain should be pausable without stopping the rest.
A validation hook is the cheapest control. Checking a domain before import removes addresses that can never receive mail, such as a domain that publishes a null MX or has no mail host. The email-domain-check product returns those signals from public DNS and open lists, and it does not contact the mail server, so it cannot prove a mailbox exists.
curl -X POST https://YOUR_API_HOST/v1/email-domain-check \
-H "Authorization: Bearer $LOOKVALID_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"info@example.com"}'
A validation call a tool could make for each new address
A captured response for that address reports null_mx: true, role_account: true and a deliverability_signal of undeliverable. A tool that cannot act on a field like that before sending fails the first row of the table.
Scoring and next step
- Run every row of the table against each candidate and record pass or fail, not a score.
- Drop any tool that fails unsubscribe headers or bounce handling, since both affect every message.
- Break ties on validation hooks and export access, which decide how well the tool fits the rest of your stack.
To wire address checks into whichever tool you pick, follow the quickstart or read the email-domain-check reference.
Sources
- RFC 8058: Signaling One-Click Functionality for List Email Headers, read 2026-10-19
- RFC 2369: The Use of URLs as Meta-Syntax for Core Mail List Commands, read 2026-10-19
- Google: Email sender guidelines, read 2026-10-19
- Yahoo Sender Hub: sender best practices, read 2026-10-19
- RFC 5322: Internet Message Format, read 2026-10-19
- RFC 3463: Enhanced Mail System Status Codes, read 2026-10-19
- RFC 5321: Simple Mail Transfer Protocol, read 2026-10-19