Email verification in Postman

Postman is where most people meet an API before they write a line against it. Two environments, one collection-level auth setting, and you can exercise every endpoint without touching your codebase.

The setup worth copying is the pair of environments. A sandbox environment pointed at a zb_test_ key lets you hammer the collection with no credits spent and no SMTP connections opened, which makes it safe to leave in a scheduled monitor.

Collection-level Bearer auth reading {{api_key}}, with environments swapping the key between live and sandbox.

Verify one address

You branch on four results: valid, invalid, catch_all and unknown. Role and disposable are boolean flags beside the result, so test them separately. Unknown is the branch most code forgets, and it is never billed.

JSON
// Environment: "ZapBounce — sandbox"
{
  "base_url": "https://api.zapbounce.com/v1",
  "api_key": "zb_test_...",        // set type: secret, not default
  "sample_email": "unknown@sandbox.zapbounce.com"
}

// Environment: "ZapBounce — live"
{
  "base_url": "https://api.zapbounce.com/v1",
  "api_key": "zb_live_...",
  "sample_email": "ada@example.com"
}

// Collection → Authorization → Bearer Token → {{api_key}}
// Every request inherits it. Nothing hardcodes a key into a saved request,
// which is what leaks when someone exports the collection to a colleague.

// POST {{base_url}}/verify
{
  "email": "{{sample_email}}"
}

Verify a list

Submit a batch rather than looping the single endpoint. We pace probes per receiving mail host, which protects the sending reputation your results depend on.

JavaScript
// Request 1 — POST {{base_url}}/batches
// Tests tab: capture the batch id for the next request in the chain.
const body = pm.response.json();

pm.test("batch accepted", () => {
    pm.response.to.have.status(200);
    pm.expect(body.batch_id).to.be.a("string");
});

// Deduplication is visible here: unique is lower than submitted on a real list.
pm.test("duplicates collapsed before billing", () => {
    pm.expect(body.unique).to.be.at.most(body.submitted);
});

pm.collectionVariables.set("batch_id", body.batch_id);

// Request 2 — GET {{base_url}}/batches/{{batch_id}}
// Pre-request: pause between polls without a fixed sleep in the runner.
setTimeout(() => {}, 5000);

// Tests: loop back to this request until the batch finishes.
const status = pm.response.json();

if (status.status !== "complete") {
    pm.execution.setNextRequest("Get batch status");   // poll again
} else {
    pm.test("coverage reported next to the verdicts", () => {
        pm.expect(status).to.have.property("coverage");
        pm.expect(status.summary).to.have.property("unknown");
    });
    pm.collectionVariables.set("results_url", status.results_url);
}

Errors and retries

A 429 and a 5xx are worth retrying. A 402 for credits and a 400 for a malformed address will fail the same way on every attempt, so stop.

JavaScript
// Tests tab on POST {{base_url}}/verify.
// The assertion that matters is the last one: unknown must never be billed.
const r = pm.response.json();

pm.test("responds in under 10 seconds", () => {
    pm.expect(pm.response.responseTime).to.be.below(10000);
});

pm.test("result is one of the four", () => {
    pm.expect(r.result).to.be.oneOf(["valid", "invalid", "catch_all", "unknown"]);
});

pm.test("role and disposable are boolean flags, not results", () => {
    pm.expect(r.role).to.be.a("boolean");
    pm.expect(r.disposable).to.be.a("boolean");
});

pm.test("unknown results are never billed", () => {
    if (r.result === "unknown") pm.expect(r.billed).to.equal(false);
});

// Error path: point the collection at an empty sandbox key and run this.
pm.test("errors carry a request id", () => {
    if (pm.response.code >= 400) {
        const { error } = pm.response.json();
        pm.expect(error).to.have.property("code");
        pm.expect(error).to.have.property("request_id");   // quote it in support
    }
});

// 429 handling in the runner: back off rather than hammering through the run.
if (pm.response.code === 429) {
    const wait = Number(pm.response.headers.get("Retry-After") ?? 5) * 1000;
    setTimeout(() => {}, wait);
    pm.execution.setNextRequest(pm.info.requestName);
}

Postman: common questions

Is there a published collection?

The requests on this page are the whole surface: verify, batches, batch status, results, files, credits, account. Build them once and the collection is done.

Can I run it as a monitor?

Yes, and use the sandbox key. A monitor on a live key spends credits on a schedule forever.

How do I test the unknown branch?

unknown@sandbox.zapbounce.com with a test key returns it on demand, which is the only reliable way to exercise that path.

Run this Postman code today

100 free checks a month, no card, credits that never expire, and unknown results that cost nothing.