Docs · Guides

Clean a list

Check every address on a list before a campaign, so the ones that would bounce never go out and your domain keeps its good name.

Preview: may change before launch · Updated

Preview

This is how the API is planned to work. Names and fields may change before launch. The APIs are in early access: join early access to get a key when they're ready.

What works today

There's no way to clean a whole list with Magpie yet: a bulk endpoint is planned but not built, and API keys aren't issued. The free email verifier checks 10 addresses a day, one at a time. In MagpieCRM on Magpie Cloud, emails found in Prospect Search are already checked as they're saved.

Before you start

  • Remove blanks and repeats first: each address checked is charged.
  • Run it as a script or a background job, not in a web request. A slow mail server can take a minute to answer.
  • Check one address at a time, or a few at once. Checks at the same company are paced on our side anyway (see Rate limits and pacing), so more at once won't make one company faster.

The code

This reads list.txt, one address a line, checks each, and writes four files. It waits and tries again on a 429 or a 5xx, and stops on anything else, such as running out of credits.

Cleaning a list
import { readFile, writeFile } from "node:fs/promises";

const MAGPIE = "https://api.magpie-cloud.com/v1";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function verify(email) {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(`${MAGPIE}/verify`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.MAGPIE_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email }),
      signal: AbortSignal.timeout(90_000), // a slow mail server can take a minute
    });
    if (res.ok) return res.json();
    const retry = res.status === 429 || res.status >= 500;
    if (!retry || attempt === 5) {
      // 400, 401 or 402 (out of credits): stop, rather than fail every address.
      throw new Error(`Stopped at ${email}: HTTP ${res.status}`);
    }
    const seconds = Number(res.headers.get("Retry-After")) || 2 ** attempt;
    await sleep(seconds * 1000);
  }
}

// One address a line. Drop blanks and repeats first: each check is charged.
const text = await readFile("list.txt", "utf8");
const emails = [...new Set(text.split("\n").map((l) => l.trim().toLowerCase()).filter(Boolean))];

const where = { valid: "keep", invalid: "remove", catch_all: "careful", risky: "careful", unknown: "later" };
const files = { keep: [], remove: [], careful: [], later: [] };

for (const email of emails) {
  const { verdict } = await verify(email);
  files[where[verdict]].push(email);
}
for (const [name, list] of Object.entries(files)) {
  await writeFile(`${name}.txt`, list.join("\n"));
}

What to do with each pile

FileVerdictsWhat to do
keep.txtvalidSend as usual.
remove.txtinvalidTake them off the list. They will bounce.
careful.txtcatch_all, riskySend in a small first batch, and the rest only if few bounce. See below.
later.txtunknownCheck again in a day or so. A mail server that didn't answer may answer next time.

Role addresses (checks.role), such as info@, can be valid but are often read by no one in particular. Whether to keep them is up to you.

Sending to catch-all addresses

A catch-all domain accepts every address when asked, then may bounce the email later. MagpieCRM on Magpie Cloud handles unconfirmed addresses like this, and you can do the same:

  1. Send to everyone in keep.txt, and a first batch of up to 50 from careful.txt.
  2. Wait at least an hour for bounces to come in.
  3. If more than 2% of that first batch hard-bounced, hold the rest. Otherwise, send them.

Take every address that hard-bounces off the list for good.

Questions about the APIs? Email pele@magpiecrm.com.