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.

By LookValid engineering. Published 2026-10-19.

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

Capabilities to test, and what a pass looks like
CapabilityHow to test itPass condition
List validation hookImport a file containing a null-MX domain, a disposable domain and a role mailboxThe tool can call an HTTP endpoint per address, or accept a result column, before sending
Per-domain throttlingQueue 200 messages to one recipient domain and watch the send logYou can set a cap per recipient domain and per sending domain, and the log shows it holding
Bounce handlingSend to an address you know is dead, then to a full mailboxPermanent failures (5.x.x) suppress the address; temporary ones (4.x.x) retry 6
Unsubscribe headersRun the script above on a real messageAll five checks print true
Suppression across campaignsUnsubscribe in one campaign, then add the address to anotherThe second campaign skips it
Spam-rate visibilityLook for per-domain complaint dataYou can see complaint rate against the 0.3 percent ceiling both Google and Yahoo publish 34
Export and API accessExport contacts and eventsEvery 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

  1. Run every row of the table against each candidate and record pass or fail, not a score.
  2. Drop any tool that fails unsubscribe headers or bounce handling, since both affect every message.
  3. 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

  1. RFC 8058: Signaling One-Click Functionality for List Email Headers, read 2026-10-19
  2. RFC 2369: The Use of URLs as Meta-Syntax for Core Mail List Commands, read 2026-10-19
  3. Google: Email sender guidelines, read 2026-10-19
  4. Yahoo Sender Hub: sender best practices, read 2026-10-19
  5. RFC 5322: Internet Message Format, read 2026-10-19
  6. RFC 3463: Enhanced Mail System Status Codes, read 2026-10-19
  7. RFC 5321: Simple Mail Transfer Protocol, read 2026-10-19