Mailbeam
Shopify + Node.jsIntermediate30 minutesUpdated January 2025

Email Verification on Shopify

Shopify doesn't expose a pre-registration hook for the storefront, but you can verify emails using two complementary approaches:

  1. App Proxy — A server endpoint that your storefront JavaScript calls before submitting the registration form
  2. 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)

Next steps