How to validate emails in Google Sheets
The free tool, a bulk CSV, or fifteen lines of Apps Script — and exactly where each one stops.
Contents
You have a column of email addresses in Google Sheets and you want to validate them without exporting to some other tool, learning a new app, or trusting a green checkmark you can't inspect. Good news first: for most of what people mean by "validate emails in Google Sheets," you don't need an API at all. Here's the honest ladder — free tool, bulk CSV, then a script — and where each rung stops.
Start with the free tool for a handful
If you have a dozen addresses, don't automate anything. Paste them into the free email validator — no signup, no key, no code. It runs the same zero-cost checks the API runs: syntax, whether the domain publishes MX records, whether it's a known disposable domain, whether it's a role address like info@ or support@. For a one-off list, that is frequently all you need, and the cheapest correct tool is the one you don't have to wire up.
The moment that stops scaling is when the column is hundreds of rows long, or you want the answer to live next to each address inside the sheet. Two ways to do that.
The whole column at once: export, bulk, re-import
For a big one-time clean, skip the per-cell wiring entirely. In Sheets, use File > Download > Comma-separated values (.csv), then POST that file straight to the bulk endpoint:
curl -X POST https://api.boundstone.io/v1/bulk/email \
-H "Authorization: Bearer bs_live_YOUR_KEY" \
-H "Content-Type: text/csv" \
--data-binary @emails.csv
You get back HTTP 202 and a job_id. When it finishes, download the results:
curl https://api.boundstone.io/v1/bulk/JOB_ID/results.csv \
-H "Authorization: Bearer bs_live_YOUR_KEY" -o results.csv
Then File > Import that CSV back as a new sheet. The free tier caps a job at 250 rows; paid keys go to 10,000. Credits are reserved per row and refunded for any row that errors, so a malformed line doesn't cost you. The full walkthrough — column layout, the result fields, handling big files — is in bulk email validation from a CSV.
Live checks in the sheet with Apps Script
There is no official Boundstone add-on for Google Sheets. What you can do is call the REST API from Google Apps Script with UrlFetchApp — that's the real, supported path, and it's about fifteen lines.
Open Extensions > Apps Script, then add a menu action that reads the selected cells and writes results beside them:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Email checks')
.addItem('Validate selection', 'validateSelection')
.addToUi();
}
function validateSelection() {
const key = PropertiesService.getScriptProperties().getProperty('BOUNDSTONE_KEY');
const sheet = SpreadsheetApp.getActiveSheet();
const range = sheet.getActiveRange();
const rows = range.getValues().map(function (row) {
const email = String(row[0]).trim();
if (!email) return ['', ''];
const res = UrlFetchApp.fetch('https://api.boundstone.io/v1/verify/email', {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + key },
payload: JSON.stringify({ email: email }),
muteHttpExceptions: true
});
const d = JSON.parse(res.getContentText());
return [d.mx_found, d.disposable];
});
sheet.getRange(range.getRow(), range.getColumn() + 1, rows.length, 2)
.setValues(rows);
}
Reload the sheet, select your email column, and run Email checks > Validate selection. Two columns appear — mx_found and disposable — straight from the API.
You might reach for a custom function instead — =VALIDATEEMAIL(A2) typed into a cell — but that door is closed, and it's worth knowing why. Sheets custom functions run without your authorization, so they can't use UrlFetchApp at all; wire one up and every cell returns You do not have permission to call UrlFetchApp.fetch. A menu action runs with your credentials when you click it, which is both the only way this works and the behavior you want when each call costs a credit — it fires once, on demand, not on every silent recalculation.
Keep the key out of the sheet
Your bs_live_ key is a server-side secret. In the script above it lives in PropertiesService (Project Settings > Script Properties > BOUNDSTONE_KEY), never in a cell and never hardcoded in the code. That matters because a shared sheet must not contain the key: paste it into a cell and everyone with view access has it.
One caveat most tutorials skip: Script Properties belong to the container-bound script, and anyone you grant edit access to the spreadsheet can open Apps Script and read them. If the sheet has several editors, run validation from a copy only you can edit, or move the call to a backend. If you'd rather script it outside Sheets entirely, the same endpoint from a server is covered in validate an email in Python.
What a "valid" actually means here
Every response tells you what it did and didn't check. For a well-formed address the API returns:
"checks": {
"performed": ["syntax", "mx", "disposable_list", "role_list"],
"not_performed": ["smtp_mailbox", "catch_all"]
}
Read the second line as carefully as the first. mx_found: true means the domain publishes mail servers in DNS — not that the specific mailbox exists. Boundstone does not perform SMTP mailbox verification or catch-all detection today, so it won't pretend a valid address is guaranteed deliverable. (free_provider comes back as its own flag, but it isn't one of the performed checks — it's a label, not a verdict.) That not_performed list is the point: a green result you can audit beats a green result you have to trust.
The short version
- A few addresses: the free email validator — no key, no code.
- A whole column, one-time: download CSV, POST to
/v1/bulk/email, importresults.csvback — see the CSV guide. - Live in-sheet: Apps Script plus
UrlFetchApp, run from a menu action, key in Script Properties. There is no native add-on — and custom functions can't make the call, so the menu action is the way. - Never put the key in a cell, and remember editors can read Script Properties too.
mx_foundis a DNS fact, not proof the mailbox exists — and the response says so.