# Validate Emails in Shopify

URL: https://email-validation.org/docs/guides/shopify/
Updated: 2026-11-06

> Documentation index: https://email-validation.org/llms.txt

Check the email address of each Shopify customer and save the result on the customer, so your segments, flows and
exports can use it. Two small pieces do the work, and you can use either or both:

- **A webhook relay** checks a customer within seconds when it is added, or when its address changes. It can also
  tag each new order with the status of its address (off by default).
- **A clean-up command** checks every customer, or those a customer search finds, that has no result yet.

Both run in your own accounts with your own keys: the relay as a Cloudflare Worker, the command with Node. They talk
to your store through a custom app that you create for your own store. There is no app to install from the Shopify
App Store.

## Before you start

- **An API key.** Create one on the [API keys page](https://email-validation.org/app/api-keys/) with the permission "Validate addresses". To clean
  more than 50 customers at once, also turn on **"Bulk lists"**. A
  [daily credit cap](https://email-validation.org/docs/authentication/#limits) limits what a busy store can spend.
- **Your store's myshopify address**, such as `acme` for `acme.myshopify.com`.
- **Node 22.18 or later** for the command, and a **Cloudflare account** for the relay.
- **The files**, which are built from our tested code:
  - [relay.js](https://email-validation.org/downloads/shopify/relay.js): the webhook relay, one Cloudflare Worker module;
  - [clean.mjs](https://email-validation.org/downloads/shopify/clean.mjs): the clean-up command and the setup, one Node module.

## Create a custom app for your store

1. In Shopify's Dev Dashboard, create an app for your store.
2. Give its version these Admin API access scopes, then release the version:

   ```text
   read_customers, write_customers
   read_orders, write_orders        (only to tag orders)
   ```

3. Install the app on your store.
4. In the app's settings, copy its **Client ID** and **Client secret**. Keep the secret safe: it lets the relay and
   the command get access tokens for your store, and Shopify signs every webhook with it.

The relay and the command each get a short-lived access token with the client ID and secret when they need one.
Shopify gives this token only when the app and the store belong to the same Shopify organization. If the setup
below stops with HTTP 400 and shop_not_permitted, create the app again from the organization that owns the store.

If your store already has a custom app that was created in the Shopify admin before 2026, you can use it instead:
set `SHOPIFY_ACCESS_TOKEN` to its Admin API access token in place of `SHOPIFY_CLIENT_ID`, and `SHOPIFY_CLIENT_SECRET`
to its API secret key. It needs the same scopes.

## Where the result goes

Each checked customer gets four metafields in the namespace `email_validation`:

| Metafield | Value |
|---|---|
| `email_validation.status` | one of the five [result statuses](https://email-validation.org/docs/statuses/): `valid`, `invalid`, `catch_all`, `unknown` or `disposable` |
| `email_validation.reason` | why, such as `accepted` or `no_mailbox` |
| `email_validation.checked_at` | when the check ran (date and time, UTC) |
| `email_validation.checked_email` | the address that was checked |

A customer is not checked again while its address is still the checked one. When you also turn on tags,
the customer gets one of the tags `email-valid`, `email-invalid`, `email-catch_all`, `email-unknown` or
`email-disposable`, and its other tags stay as they are.

## Check new and changed customers

1. Deploy the relay to your Cloudflare account and give it its secrets:

   ```bash
   npx wrangler deploy relay.js --name ev-shopify-relay --compatibility-date 2026-10-01 --var SHOPIFY_SHOP:acme
   npx wrangler secret put EV_API_KEY --name ev-shopify-relay
   npx wrangler secret put SHOPIFY_CLIENT_ID --name ev-shopify-relay
   npx wrangler secret put SHOPIFY_CLIENT_SECRET --name ev-shopify-relay
   ```

   Your keys stay in the Worker's secret store. For [EU data residency](https://email-validation.org/docs/eu-data-residency/), add `--var EV_REGION:eu` to
   the deploy command. To tag customers as well, add `--var SHOPIFY_CUSTOMER_TAGS:on`.
2. Create the metafield definitions and subscribe the relay to your store's customer webhooks:

   ```bash
   export SHOPIFY_SHOP=acme  SHOPIFY_CLIENT_ID=…  SHOPIFY_CLIENT_SECRET=…
   node clean.mjs --setup --relay-url https://ev-shopify-relay.<your-subdomain>.workers.dev/
   ```

   The definitions give the four metafields their names on the customer page. The command subscribes the relay to
   customer creation and customer updates, and leaves anything that is already there as it is.
3. Add a test customer with the address `no_mailbox@sandbox.email-validation.org`, a [test address](https://email-validation.org/docs/test-addresses/)
   that costs nothing. Within a few seconds its Email status metafield is `invalid`. Then delete the test customer.

The relay checks a customer when it is created, and when its address changes. It ignores every other change,
including its own write to the metafields, so a result never starts a second check. Before it checks, it reads the
customer, so it always checks the address as it is now. The relay refuses any request that Shopify did not sign
with your app's client secret.

Shopify waits only 5 seconds for an answer, so the relay answers at once and does the check just after. If a check
cannot run, for example because the key has no credits left, Shopify does not deliver the event again: run the
clean-up command to catch those customers up.

## Tag new orders (optional)

The relay can tag each new order with the status of its address, such as `email-invalid`, so your team or a flow
can look at it before fulfilling. When the order's address is its customer's and the customer already has a result,
the order gets that result without a new check. Otherwise the relay checks the address, and also saves the result on
the customer when the addresses match.

1. Give the app the two order scopes listed above too, and release that version.
2. Add `--var SHOPIFY_ORDER_TAGS:on` to the relay's deploy command, and deploy it again.
3. Run the setup again with `--orders`:

   ```bash
   node clean.mjs --setup --relay-url https://ev-shopify-relay.<your-subdomain>.workers.dev/ --orders
   ```

This tags orders after they are placed. It does not stop a checkout.

## Clean your customers

```bash
export EV_API_KEY=…  SHOPIFY_SHOP=acme  SHOPIFY_CLIENT_ID=…  SHOPIFY_CLIENT_SECRET=…
node clean.mjs --dry-run    # counts what it would check
node clean.mjs              # checks and writes the results
```

The command skips customers without an address and customers that already have a status other than `unknown` for
the address they have now (add `--revalidate` to check them again). To check only some customers, pass a Shopify
customer search, such as `--query "tag:newsletter"`. Up to 50 customers are checked one by one. More go
into one [bulk job](https://email-validation.org/docs/bulk/): the command uploads the list, starts it, waits for it, and writes each result to
its customer.

If a run stops for any reason, such as no credits or a closed laptop, run the same command again. It keeps the bulk
job's id in a small state file (ids only, no addresses) and resumes that job, so a list is never paid for twice.
Exit code `75` means the job was still running when the command stopped waiting: run it again later.

Other options: `--region eu` for [EU data residency](https://email-validation.org/docs/eu-data-residency/), `--tags` to tag customers as well, and
`--wait-minutes` for how long to wait for a bulk job (default 240).

## Costs and limits

- Each checked customer is one check. Results with the status `unknown` are free, and so are the
  [test addresses](https://email-validation.org/docs/test-addresses/).
- Shopify limits how much API work an app can do per second. The command and the relay follow the limit that
  Shopify reports with each answer, and wait when Shopify asks them to.
- Our own [rate limits](https://email-validation.org/docs/rate-limits/) apply to the relay as to any other client.
