Technical

A step by step order for cleaning an email list

List cleaning goes faster and costs less when the cheap steps run before the expensive ones. Each step removes rows that the next step would otherwise spend time on. This post gives an order that works, the standards behind the syntax rules, and an outline for running the domain step as a bulk job from the dashboard.

By LookValid engineering. Published 2026-10-19.

Step 1: syntax and length

Start with rules that need no network. The SMTP specification sets the maximum length of a local-part at 64 octets and of a domain name at 255 octets 1. The Internet Message Format defines the local-part as a dot-atom or a quoted string, and says the dot-atom form should be used and the quoted-string form should not 2. A dot-atom is a run of allowed characters separated by single dots, so a local part with a doubled dot, a leading dot or a space is not a normal address.

Our own input check is stricter than the grammar in two ways worth knowing: it rejects an address longer than 254 characters and any local part over 64, and it does not accept quoted local parts or domain literals such as a@[192.0.2.1]. Those rows come back as invalid_input, and a row that fails this way is not charged.

Step 2: normalize, then deduplicate

Deduplicate only after normalizing, or near-duplicates survive. Trim whitespace and lowercase the domain. The local part needs a decision: the SMTP specification says a local-part must be treated as case sensitive, and also says that exploiting that is discouraged 1. In a marketing list, lowercasing the local part is the practical choice, because two addresses that differ only by case almost certainly reach the same person. Our check lowercases both parts before it looks anything up.

  • Keep the first occurrence of each normalized address and record where the others came from.
  • Do not strip plus tags or dots from the local part: that is a rule of some providers, not of the standard.
  • Keep the original value in a separate column so you can trace a result back.

Steps 3 and 4: domains, then role accounts

The domain step asks whether the part after the @ can receive mail at all. A domain that publishes a null MX is announcing that it accepts no mail 3. The email-domain-check product reads the domain's MX, SPF and DMARC records and open disposable-domain and free-provider lists, and sets deliverability_signal to good, risky or undeliverable. It does not contact the mail server, so it cannot confirm that a mailbox exists.

Role accounts such as info@ or support@ belong to a team rather than a person. The same check sets role_account when the local part matches its role list, which lets you route those rows to a separate segment instead of deleting them.

Cleaning order and what each step removes
StepCheckNeeds networkAction on a hit
1Syntax and length limitsNoRemove or send back for correction
2Normalize and deduplicateNoKeep one row per address
3Domain exists and accepts mailYes, DNSRemove undeliverable
4Disposable domain, role accountYes, DNS and listsSegment or remove
5First send in small batchesYour mail systemRemove addresses that bounce

Cleaning order and what each step removes

Run steps 3 and 4 as a bulk job

The dashboard runs a CSV through a product in the background. The limits below are read from the same configuration the service enforces. See the bulk CSV jobs guide for the full list.

Bulk job limits that matter for a list
LimitValue
Rows in one file10,000
File size5 MB
Jobs queued or running at once3
Jobs started per day30

Bulk job limits that matter for a list

  1. Prepare a CSV with one email column that has already passed steps 1 and 2.
  2. Split the file if it is over the row or size limit.
  3. Open jobs in the dashboard, upload the file, choose email-domain-check and the email column.
  4. Start the job and download the results when it shows completed.
  5. Filter on deliverability_signal and role_account, then send to the rest in small batches.
const seen = new Set();
const keep = [];
for (const raw of rows) {
  const email = raw.trim().toLowerCase();
  const at = email.lastIndexOf('@');
  if (at < 1 || at === email.length - 1) continue; // no local part or no domain
  if (email.slice(0, at).length > 64) continue;    // local-part limit
  if (email.slice(at + 1).length > 255) continue;  // domain limit
  if (seen.has(email)) continue;                   // duplicate after normalizing
  seen.add(email);
  keep.push(email);
}

Steps 1 and 2 in a few lines, before you upload

Check one address before you run the job

Before you spend a job on a file, try the check on a single address in the free email domain checker to see the fields you will filter on. When the list is clean, set up the same call for new signups through the email domain check product page.

Sources

  1. RFC 5321: Simple Mail Transfer Protocol, read 2026-10-19
  2. RFC 5322: Internet Message Format, read 2026-10-19
  3. RFC 7505: A "Null MX" No Service Resource Record for Domains That Accept No Mail, read 2026-10-19