All posts

How to Verify Emails in a Postgres or MySQL Table Before You Send

··6 min read

To verify emails in a Postgres or MySQL table, export the distinct addresses to a CSV, upload the file to an email verifier, and load the results back into a separate table keyed on the email. The database can only check that text looks like an address; it cannot ask the recipient's server whether the mailbox exists. SimpleVerifier does that. Upload on the verify page, download valid, risky and invalid with a reason, and join the result when you build a send list. Pro is $39 a month, and 100 checks are free.

Both databases are named here because the workflow is the same. The SQL below is generic; check your database's documentation for export commands and permissions, since they differ between Postgres and MySQL and between hosted services.

Why check a database before you mail it

A table of users, leads or newsletter subscribers accumulates bad addresses from every direction: typos at sign-up, disposable inboxes, bots, and people who left their employer years ago. Sending a campaign to the whole table produces bounces, and a high bounce rate damages the sender reputation of the domain you mail from. It also makes your next campaign land in spam more often. See why emails bounce and how often to verify an email list.

A transactional email, such as a password reset to a typo address, is lost too. If you notice a lot of failed sends to sign-ups, the fix starts at the form: see signup protection.

Step 1: Pull distinct, clean addresses

Select what you need and nothing more. Distinct, trimmed and lower-cased addresses save checks and make the join later easy:

SELECT DISTINCT lower(trim(email)) AS email
FROM users
WHERE email IS NOT NULL AND email <> '';

The same query works in MySQL. Leave out names, phone numbers and anything else personal; a verification upload only needs the address. To limit the pass to people you have not checked yet, add a condition against the results table you will create in step 3.

Export the result to CSV with your usual tool: your database client's export, a GUI from your host, or a script. Open the file and look at it; check that the header is email and that the encoding is UTF-8. If something looks off, the CSV encoding fixer repairs it.

Step 2: Verify the CSV

  1. Open the verify page and sign in with Google. The first 100 checks are free, no card.
  2. Upload the CSV. If there is more than one candidate column, the page asks which to use.
  3. Wait for completion and download the results. Each row carries a status, a reason and a send-advice column.

For large tables this is the right path, and it is the one Pro is built for: the first 100,000 checks a month run at full speed, then slower, never cut off. See how long bulk verification takes for what to expect with big files. The CSV email verifier page covers the file format.

Status Meaning What to do
valid The mail server confirmed the mailbox exists. Safe to send.
risky Delivery is not confirmed: a catch-all domain, a role address such as info@, or a provider that will not say. Keep out of large sends. Test in a small batch.
invalid Bad format, no mail server, disposable address or mailbox not found. Do not send.

Step 3: Load the results into a separate table

CREATE TABLE email_checks (
  email      varchar(320) PRIMARY KEY,
  status     varchar(16) NOT NULL,
  reason     varchar(255),
  checked_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Import the results file into it with your database's CSV import, mapping the email, status and reason columns. If the downloaded CSV has extra columns, load it into a staging table first and insert the three you need.

Why a separate table:

  • You keep history. A status is a snapshot. Addresses go stale, and the same mailbox that was valid in spring may be closed by winter.
  • You do not touch production data. Your users table keeps its shape, and no migration is needed.
  • The join is trivial.

Step 4: Build the send list from valid only

SELECT u.id, u.email
FROM users u
JOIN email_checks c ON lower(trim(u.email)) = c.email
WHERE c.status = 'valid';

Use status = 'risky' to build the second, smaller list. A risky result means delivery is not confirmed: often a catch-all domain that accepts every address, a role address such as info@, or a provider that will not reveal mailbox status. Send those separately, small, and watch the bounces. Anything invalid stays out. Addresses that were never checked have no row, so an inner join excludes them automatically, which is the safe default.

Step 5: Check new rows as they arrive

An export is a snapshot. For rows added after it, check in your application code at the point of sign-up or import. The REST API returns a verdict for one address per call:

GET https://simpleverifier.com/api/v1/verify?email=jane@company.com
Authorization: Bearer sv_live_...

Create a key on the API keys page and keep it on the server. Call it from your backend, never from the browser. Block invalid at sign-up, let risky through with a flag, and let timeouts through so a slow mail server does not stop a real person registering. Then write the answer into email_checks. The API docs have the fields, and the language guides, such as Python, Node.js, PHP and Laravel, have worked code. Pro includes 10,000 API calls a month at 120 calls a minute per key.

A nightly job can also take rows with no entry in email_checks, export them, and check them as a small CSV. Both approaches work; pick the one your team already operates.

Mistakes to avoid

  • Mailing rows you never checked. A left join that treats a missing row as fine defeats the purpose.
  • Trusting an old result forever. Re-check before any large campaign or after a long pause.
  • Marking a timeout as valid. A failed or unanswered check is not a verdict. Keep those rows out of the send, and retry them.
  • Exporting more than you need. Names and phone numbers do not help verification; leave them out.
  • Deleting invalid users blindly. An invalid address on an active account may be a typo the user can fix by updating their profile in the app. Do not try to email them to ask.

Which list to check first

If you have limited time, check the rows you are about to mail, not the whole table. Start with the newest sign-ups, which are the most likely to hold typos and bot entries, then work back through older rows. Old addresses are the likeliest to have gone stale. A table with several years of history can have both problems at once, which is why the first full pass is worth doing before the next big send.

What it costs

SimpleVerifier Pro is $39 a month. The first 100,000 checks a month run at full speed, then they run slower, and you are never cut off. You can try 100 free checks first by signing in with Google, no card. See pricing for the other plans. API access comes with Pro, which includes 10,000 calls a month.

Bottom line

SQL can find duplicates and bad formatting, but it cannot tell you a mailbox exists. Export distinct addresses, verify the CSV, store the results in their own table and join on valid when you build a send. Run the 100 free checks on a slice of the table first and see what share comes back risky or invalid.

Common questions

Can Postgres or MySQL verify email addresses?

Only the shape. A CHECK constraint or regex can reject text that is not formatted like an address. Whether the mailbox exists is a question for the recipient's mail server, so you need an external verification step.

How do I get emails out of my database for verification?

Run a query that selects distinct, lower-cased, trimmed addresses and export the result to CSV with your database's own export command or client. Check your database's documentation for the exact syntax and permissions.

Should I add a status column to my users table?

A separate table is usually safer: one row per address with status, reason and the date checked. You can re-verify later without touching the users table and join it in when you build a send list.

When should I use the API instead of a CSV?

Use the CSV upload for a large existing table. Use the API for new sign-ups and imports as they arrive, from your application code. Pro includes 10,000 API calls a month.

How much does it cost?

100 free checks with a Google sign-in. Pro is $39 a month, with the first 100,000 CSV checks a month at full speed, then slower, never cut off.

Check your list before you send

Real mailbox checks over SMTP, and nothing is sent to the address. Sign in with Google and your first 100 email checks are free, no card. Plans for bigger lists are on the pricing page.