Email Verification on Shopify
Shopify doesn't expose a pre-registration hook for the storefront, but you can verify emails using two complementary approaches:
- App Proxy — A server endpoint that your storefront JavaScript calls before submitting the registration form
- Customers Create Webhook — A webhook that fires after a customer is created, allowing you to tag or flag invalid accounts
This tutorial covers both patterns.
Prerequisites
- Shopify store with Partner account
- Node.js app (Express or similar) deployed publicly
- A Mailbeam API key (sign up free)
Option A — App Proxy (real-time, pre-registration)
An App Proxy routes requests from {your-store}.myshopify.com/apps/verify-email to your server.
Set up the App Proxy
In your Shopify Partner app settings → App proxy:
- Subpath:
apps - Subpath prefix:
verify-email - Proxy URL:
https://your-server.com/api/verify-email
The proxy endpoint
// server/routes/shopifyVerify.js
import express from "express";
import Mailbeam from "@mailbeam/sdk";
const router = express.Router();
const mb = new Mailbeam({ apiKey: process.env.MAILBEAM_KEY });
router.post("/api/verify-email", async (req, res) => {
const { email } = req.body;
if (!email) {
return res.json({ valid: false, error: "Email is required." });
}
try {
const { valid, score, reason } = await mb.verify(email);
if (!valid || score < 60) {
const message =
reason === "disposable_domain"
? "Please use a permanent email address."
: "Please provide a valid email address.";
return res.json({ valid: false, error: message });
}
res.json({ valid: true });
} catch {
// Fail open — don't block registration
res.json({ valid: true });
}
});
export default router;Storefront JavaScript
Add this to your Shopify theme's registration template:
// assets/mailbeam-verify.js
(function () {
const form = document.querySelector('form[action="/account"]');
const emailInput = document.querySelector('input[name="customer[email]"]');
if (!form || !emailInput) return;
form.addEventListener('submit', async function (event) {
// Only intercept registration forms
const actionInput = form.querySelector('input[name="form_type"]');
if (!actionInput || actionInput.value !== 'create_customer') return;
event.preventDefault();
const email = emailInput.value.trim();
try {
const res = await fetch('/apps/verify-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
const { valid, error } = await res.json();
if (!valid) {
showError(emailInput, error || 'Please use a valid email address.');
return;
}
} catch {
// Fail open on network errors
}
form.submit();
});
function showError(input, message) {
let errorEl = document.getElementById('mailbeam-email-error');
if (!errorEl) {
errorEl = document.createElement('p');
errorEl.id = 'mailbeam-email-error';
errorEl.style.cssText = 'color: red; font-size: 0.875rem; margin-top: 0.25rem;';
input.parentNode.insertBefore(errorEl, input.nextSibling);
}
errorEl.textContent = message;
input.focus();
}
})();Option B — customers/create webhook (post-registration)
This pattern runs after Shopify creates the customer. Useful for tagging suspicious accounts:
// server/routes/shopifyWebhook.js
import crypto from "crypto";
import Mailbeam from "@mailbeam/sdk";
const mb = new Mailbeam({ apiKey: process.env.MAILBEAM_KEY });
router.post("/webhooks/customers/create", express.raw({ type: "application/json" }), async (req, res) => {
// Verify the webhook signature
const hmac = req.headers["x-shopify-hmac-sha256"];
const hash = crypto
.createHmac("sha256", process.env.SHOPIFY_WEBHOOK_SECRET)
.update(req.body)
.digest("base64");
if (hmac !== hash) {
return res.status(401).send("Unauthorized");
}
const customer = JSON.parse(req.body.toString());
const { email, id: customerId } = customer;
try {
const { valid, score } = await mb.verify(email);
if (!valid || score < 40) {
// Tag the customer for review
await tagShopifyCustomer(customerId, ["suspicious-email", "requires-review"]);
}
} catch {
// Log but don't fail the webhook
console.error("[Mailbeam] verification error");
}
res.status(200).send("OK");
});Best practices
- Use App Proxy for real-time prevention, webhook for auditing
- Always return
{ valid: true }in catch blocks — fail open - Shopify HMAC verification is required for webhooks (security best practice)