Shopify
Email checker for Shopify. Verify customer emails at checkout and in Shopify apps.
Protect your Shopify store from fake accounts, reduce cart abandonment email bounces, and improve customer communication by verifying email addresses.
Why Verify Emails in Shopify?
| Challenge | Impact | Solution |
|---|---|---|
| Fake accounts | Promo abuse, fraud | Verify at registration |
| Cart abandonment | Bounced recovery emails | Verify before sending |
| Order notifications | Failed delivery updates | Verify at checkout |
| Marketing campaigns | Low deliverability | Clean customer list |
Integration Methods
| Method | Best For | Complexity |
|---|---|---|
| Shopify Flow | Automated workflows | Low |
| Shopify Functions | Checkout validation | Medium |
| Third-party App | Full solution | Low |
| Custom App | Complete control | High |
Method 1: Shopify Flow (Recommended)
Use Shopify Flow to verify emails automatically.
Verify New Customer Emails
Create a workflow to verify emails when customers register:
Trigger: Customer created
Condition: Customer email is not blank
Actions:
- Send HTTP request to EmailVerify
- Add customer tag based on result
Flow Configuration
Workflow: Verify New Customer Email
Trigger:
Event: Customer created
Condition:
- Customer email is not blank
Action 1:
Type: Send HTTP request
URL: https://api.emailverify.ai/v1/verify
Method: POST
Headers:
- Authorization: Bearer YOUR_API_KEY
- Content-Type: application/json
Body: {"email": "{{customer.email}}"}
Wait:
Duration: 1 second
Action 2:
Type: Add customer tags
Tags:
- email_verified (if status = valid)
- email_invalid (if status = invalid)Verify Before Cart Abandonment Emails
Workflow: Verify Before Abandonment Email
Trigger:
Event: Checkout abandoned
Delay: 1 hour
Condition:
- Customer email is not blank
- Customer does not have tag "email_invalid"
Action 1:
Type: Send HTTP request to EmailVerify
Body: {"email": "{{checkout.email}}"}
Action 2:
Type: Branch
If status = "valid":
- Continue to abandonment email sequence
If status = "invalid":
- Add tag "email_invalid"
- Do not send emailMethod 2: Checkout Validation with Shopify Functions
Create a Shopify Function to validate emails during checkout.
Step 1: Create a Cart Transform Function
// extensions/email-validation/src/run.js
import { EmailVerify } from '@emailverify/node';
export function run(input) {
const { cart } = input;
const email = cart?.buyerIdentity?.email;
if (!email) {
return { operations: [] };
}
// Note: For real-time validation, use a pre-validated cache
// or implement async validation via metafield
return {
operations: [],
};
}Step 2: Create a Checkout UI Extension
// extensions/email-validation-ui/src/Checkout.jsx
import {
useExtensionApi,
render,
Banner,
BlockStack,
} from '@shopify/checkout-ui-extensions-react';
import { useState, useEffect } from 'react';
render('Checkout::Contact::RenderAfter', () => <EmailValidation />);
function EmailValidation() {
const { buyerIdentity } = useExtensionApi();
const [validationStatus, setValidationStatus] = useState(null);
useEffect(() => {
const email = buyerIdentity?.email?.current;
if (email) {
validateEmail(email).then(setValidationStatus);
}
}, [buyerIdentity?.email?.current]);
if (!validationStatus) return null;
if (validationStatus.status === 'invalid') {
return (
<Banner status="warning">
Please check your email address. It appears to be invalid.
</Banner>
);
}
if (validationStatus.result?.disposable) {
return (
<Banner status="info">
We recommend using a permanent email for order updates.
</Banner>
);
}
return null;
}
async function validateEmail(email) {
const response = await fetch('/apps/email-verify/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
return response.json();
}Method 3: Custom Shopify App
Build a complete email verification solution.
App Backend (Node.js)
// server/index.js
import '@shopify/shopify-app-remix/adapters/node';
import { shopifyApp } from '@shopify/shopify-app-remix/server';
import { EmailVerify } from '@emailverify/node';
const shopify = shopifyApp({
// ... Shopify config
});
const emailVerify = new EmailVerify({
apiKey: process.env.EMAILVERIFY_API_KEY,
});
// API route for email verification
export async function action({ request }) {
const { email, customerId } = await request.json();
try {
const result = await emailVerify.verify(email);
// Update customer metafield
if (customerId) {
await updateCustomerVerificationStatus(customerId, result);
}
return json(result);
} catch (error) {
return json({ error: error.message }, { status: 500 });
}
}
async function updateCustomerVerificationStatus(customerId, result) {
const { admin } = await shopify.authenticate.admin(request);
await admin.graphql(`
mutation updateCustomerMetafield($input: CustomerInput!) {
customerUpdate(input: $input) {
customer {
id
}
}
}
`, {
variables: {
input: {
id: `gid://shopify/Customer/${customerId}`,
metafields: [
{
namespace: "email_verification",
key: "status",
value: result.status,
type: "single_line_text_field"
},
{
namespace: "email_verification",
key: "score",
value: String(result.score),
type: "number_decimal"
},
{
namespace: "email_verification",
key: "verified_at",
value: new Date().toISOString(),
type: "date_time"
}
]
}
}
});
}Webhook Handler
Handle customer creation webhooks:
// server/webhooks/customer-created.js
export async function handleCustomerCreated(topic, shop, body) {
const customer = JSON.parse(body);
const { id, email } = customer;
if (!email) return;
try {
// Verify email
const result = await emailVerify.verify(email);
// Update customer with tags
const tags = [];
if (result.status === 'valid') {
tags.push('email_verified');
} else if (result.status === 'invalid') {
tags.push('email_invalid');
}
if (result.result?.disposable) {
tags.push('disposable_email');
}
await updateCustomerTags(shop, id, tags);
// Store verification result
await updateCustomerVerificationStatus(shop, id, result);
console.log(`Verified ${email}: ${result.status}`);
} catch (error) {
console.error(`Failed to verify ${email}:`, error);
}
}Theme Integration (Liquid)
Add verification to registration form:
{% comment %} snippets/email-verification.liquid {% endcomment %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const emailInput = document.querySelector('input[type="email"]');
const submitButton = document.querySelector('form[action="/account"] button[type="submit"]');
let verificationResult = null;
emailInput.addEventListener('blur', async function() {
const email = this.value;
if (!email) return;
// Show loading state
emailInput.classList.add('verifying');
try {
const response = await fetch('/apps/emailverify/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
verificationResult = await response.json();
// Update UI based on result
updateEmailFieldUI(verificationResult);
} catch (error) {
console.error('Verification failed:', error);
} finally {
emailInput.classList.remove('verifying');
}
});
function updateEmailFieldUI(result) {
// Remove existing messages
const existingMessage = document.querySelector('.email-verification-message');
if (existingMessage) existingMessage.remove();
// Create message element
const message = document.createElement('div');
message.className = 'email-verification-message';
if (result.status === 'invalid') {
message.classList.add('error');
message.textContent = 'Please enter a valid email address';
submitButton.disabled = true;
} else if (result.result?.disposable) {
message.classList.add('warning');
message.textContent = 'Please use a permanent email for account recovery';
} else if (result.status === 'valid') {
message.classList.add('success');
message.textContent = '✓ Email verified';
submitButton.disabled = false;
}
emailInput.parentNode.appendChild(message);
}
});
</script>
<style>
.email-verification-message {
font-size: 12px;
margin-top: 4px;
}
.email-verification-message.error { color: #c9302c; }
.email-verification-message.warning { color: #f0ad4e; }
.email-verification-message.success { color: #5cb85c; }
input[type="email"].verifying {
background-image: url('/path/to/spinner.gif');
background-position: right 10px center;
background-repeat: no-repeat;
}
</style>Use Cases
1. Prevent Fake Account Registration
Block disposable and invalid emails during signup:
// Theme app extension
async function validateRegistration(email) {
const result = await verifyEmail(email);
if (result.status === 'invalid') {
return {
valid: false,
message: 'Please enter a valid email address',
};
}
if (result.result.disposable) {
return {
valid: false,
message: 'Temporary email addresses are not allowed',
};
}
return { valid: true };
}2. Cart Abandonment Recovery
Only send abandonment emails to valid addresses:
Shopify Flow:
Trigger: Checkout abandoned (1 hour delay)
Condition: Check email verification status
If valid:
→ Send abandonment email
→ Add to remarketing audience
If invalid:
→ Skip email
→ Log for analytics3. Order Risk Assessment
Factor email quality into fraud detection:
function calculateOrderRiskScore(order, emailVerification) {
let riskScore = 0;
// Email verification factors
if (emailVerification.status === 'invalid') {
riskScore += 30;
}
if (emailVerification.result?.disposable) {
riskScore += 20;
}
if (emailVerification.result?.free && order.total > 500) {
riskScore += 10; // High value order with free email
}
// Other factors...
if (order.billing_address !== order.shipping_address) {
riskScore += 15;
}
return riskScore;
}4. Customer Segmentation
Create customer segments based on email quality:
| Segment | Criteria | Marketing Strategy |
|---|---|---|
| High Value | Verified, business email | Premium campaigns |
| Standard | Verified, free email | Regular campaigns |
| At Risk | Unverified, old account | Re-verification campaign |
| Excluded | Invalid, disposable | No marketing |
Metafield Setup
Create metafields to store verification data:
Customer Metafields
mutation createMetafieldDefinitions {
metafieldDefinitionCreate(definition: {
namespace: "email_verification"
key: "status"
name: "Email Verification Status"
type: "single_line_text_field"
ownerType: CUSTOMER
}) {
createdDefinition { id }
}
}Recommended metafields:
| Namespace | Key | Type | Description |
|---|---|---|---|
| email_verification | status | single_line_text_field | valid, invalid, unknown |
| email_verification | score | number_decimal | 0.0 - 1.0 |
| email_verification | verified_at | date_time | Last verification date |
| email_verification | disposable | boolean | Is disposable email |
| email_verification | role_based | boolean | Is role-based email |
Access Metafields in Liquid
{% if customer.metafields.email_verification.status == 'valid' %}
<span class="verified-badge">✓ Verified</span>
{% endif %}Bulk Customer Verification
Clean your existing customer list:
Export Customers
async function exportCustomersForVerification(admin) {
const query = `
query getCustomers($cursor: String) {
customers(first: 250, after: $cursor) {
edges {
node {
id
email
createdAt
metafield(namespace: "email_verification", key: "status") {
value
}
}
cursor
}
pageInfo {
hasNextPage
}
}
}
`;
let customers = [];
let cursor = null;
do {
const response = await admin.graphql(query, {
variables: { cursor },
});
const { edges, pageInfo } = response.data.customers;
// Filter unverified customers
const unverified = edges
.filter((e) => !e.node.metafield)
.map((e) => ({
id: e.node.id,
email: e.node.email,
}));
customers.push(...unverified);
cursor = edges[edges.length - 1]?.cursor;
} while (response.data.customers.pageInfo.hasNextPage);
return customers;
}Bulk Verify and Update
async function bulkVerifyCustomers(customers) {
const emails = customers.map((c) => c.email);
// Submit bulk verification job
const job = await emailVerify.verifyBulk(emails);
// Wait for completion
const results = await waitForJobCompletion(job.job_id);
// Update customers with results
for (const result of results) {
const customer = customers.find((c) => c.email === result.email);
if (customer) {
await updateCustomerVerificationStatus(customer.id, result);
}
}
return results;
}Best Practices
1. Verify at Multiple Points
- Registration: Block fake accounts
- Checkout: Ensure order notifications reach customers
- Cart abandonment: Don't waste emails on invalid addresses
2. Cache Results
Cache verification results to avoid redundant API calls:
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
async function verifyWithCache(email) {
const cacheKey = `email_verify:${email}`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const result = await emailVerify.verify(email);
await redis.setex(cacheKey, CACHE_DURATION / 1000, JSON.stringify(result));
return result;
}3. Handle Edge Cases
function handleVerificationResult(result, context) {
switch (result.status) {
case 'valid':
// Normal flow
break;
case 'invalid':
if (context === 'checkout') {
// Don't block checkout, just log
logInvalidEmail(result.email, 'checkout');
} else if (context === 'registration') {
// Block registration
throw new Error('Invalid email');
}
break;
case 'unknown':
// Accept but flag for review
flagForReview(result.email);
break;
case 'accept_all':
// Valid but monitor for bounces
markAsCatchAll(result.email);
break;
}
}4. Monitor and Optimize
Track these metrics:
- Verification success rate
- Bounce rate reduction
- Fake account prevention rate
- Cart abandonment email deliverability