ZapBounce and Redshift

Redshift moves bulk data through S3 with UNLOAD and COPY, and that is exactly the shape a verification job wants.

Unload the candidate addresses, verify the file, and copy the results back. No row-by-row calls and no external function complexity.

Row-by-row anything is wrong in Redshift

Redshift is a columnar warehouse built for bulk operations. Single-row inserts and updates are slow and produce table bloat that needs vacuuming.

A verification job that updates a verdict per row will be slower than the verification itself and will leave the table needing maintenance.

UNLOAD and COPY through S3 sidesteps all of it, and it is the pattern the warehouse is designed around.

How the data moves

  1. UNLOAD candidates to S3

    A query selecting addresses with no verdict or a stale one, written straight to S3 as CSV.

  2. Verify the file with the file upload endpoint

    The file is already in the right shape, and your other columns come back untouched.

  3. COPY the results into a staging table

    Then a single MERGE or an insert-and-swap into the verdict table.

  4. Vacuum and analyze after the load

    Standard Redshift hygiene after a bulk change, and easy to forget in an automated job.

Setting it up

  1. Create a verdict table with address, verdict, check date and coverage columns.
  2. Write an UNLOAD query selecting candidates to an S3 prefix.
  3. Verify the unloaded file through the file upload endpoint.
  4. COPY the results file into a staging table.
  5. MERGE the staging table into the verdict table in one operation.
  6. Run VACUUM and ANALYZE after the load, as part of the job rather than separately.

Redshift: common questions

Can I verify in real time from Redshift?

No, and you should not want to. Redshift is an analytics warehouse; real-time checks belong in the application.

MERGE or insert-and-swap?

MERGE where your Redshift version supports it. Either works; the point is one bulk operation rather than per-row updates.

Do I need to vacuum?

After a bulk update, yes. Build it into the job, because a forgotten vacuum shows up as a slow query weeks later.

A weekly job against a 2.4 million row contacts table

Say your contacts table holds 2.4 million rows, and each week about 35,000 of them are new or carry a verdict older than 90 days. The job starts by unloading only those candidates: UNLOAD ('select email from contacts_to_check') TO 's3://your-bucket/verify/2026-09-19/' IAM_ROLE 'your-role-arn' CSV HEADER PARALLEL OFF GZIP.

PARALLEL OFF matters here. By default Redshift writes files per slice, which leaves you a folder of fragments to stitch together. With it off you get a single file (Redshift only splits past 6.2 GB, far above this job), and GZIP keeps the upload small. Our file endpoint accepts gzip directly.

When the batch finishes, put the results CSV under the same S3 prefix and load it: COPY verify_staging FROM 's3://your-bucket/verify/2026-09-19/results.csv' IAM_ROLE 'your-role-arn' CSV IGNOREHEADER 1. One MERGE from verify_staging into the verdict table finishes the job.

Modeling the verdict so nobody flattens it

Store the result as a string column holding the four values the API returns (valid, invalid, catch_all, unknown), with role, disposable and free_provider as boolean columns beside it, plus the date your job ran and whether the row was billed. Resist adding an is_valid boolean. Once it exists every dashboard joins on it, and catch_all and unknown both quietly become false or, worse, true.

A view does the convenience job better. Define something like sendable_contacts as valid plus whatever else your policy allows, so the definition lives in one place. If marketing later decides to mail catch-all addresses as a separate segment, that's a one-line change to a view, not a backfill.

Keep history too. Append each run's results to a verdict_history table before the MERGE overwrites the current value. Addresses move from valid to invalid as people change jobs, and the rate at which that happens in your own data tells you how often the job needs to run.

If the contacts table is distributed on email, give the staging table the same distribution key. A MERGE between two tables distributed on the join column avoids shuffling rows across nodes.

Check a Redshift export today

100 free checks a month, no card. Unknown results and duplicates are never billed.