Shopify
Email checker for Shopify. Verify customer emails at checkout and in Shopify apps.
가짜 계정으로부터 Shopify 스토어를 보호하고, 장바구니 이탈 이메일 반송을 줄이고, 이메일 주소를 검증하여 고객 커뮤니케이션을 개선하세요.
Shopify에서 이메일을 검증하는 이유
| 과제 | 영향 | 솔루션 |
|---|---|---|
| 가짜 계정 | 프로모션 남용, 사기 | 등록 시 검증 |
| 장바구니 이탈 | 반송된 복구 이메일 | 발송 전 검증 |
| 주문 알림 | 배송 업데이트 실패 | 결제 시 검증 |
| 마케팅 캠페인 | 낮은 전달률 | 고객 목록 정리 |
통합 방법
| 방법 | 적합 대상 | 복잡도 |
|---|---|---|
| Shopify Flow | 자동화된 워크플로 | 낮음 |
| Shopify Functions | 결제 검증 | 중간 |
| 서드파티 앱 | 완전한 솔루션 | 낮음 |
| 커스텀 앱 | 완전한 제어 | 높음 |
방법 1: Shopify Flow (권장)
Shopify Flow를 사용하여 이메일을 자동으로 검증합니다.
새 고객 이메일 검증
고객이 등록할 때 이메일을 검증하는 워크플로 생성:
트리거: Customer created
조건: Customer email is not blank
액션:
- EmailVerify로 HTTP 요청 전송
- 결과에 따라 고객 태그 추가
Flow 구성
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)장바구니 이탈 이메일 전송 전 검증
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 email방법 2: Shopify Functions로 결제 검증
결제 중 이메일을 검증하는 Shopify Function을 생성합니다.
1단계: 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: [],
};
}2단계: 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();
}방법 3: 커스텀 Shopify 앱
완전한 이메일 검증 솔루션을 구축합니다.
앱 백엔드 (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"
}
]
}
}
});
}웹훅 핸들러
고객 생성 웹훅 처리:
// 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);
}
}테마 통합 (Liquid)
등록 폼에 검증 추가:
{% 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>사용 사례
1. 가짜 계정 등록 방지
가입 시 일회용 및 무효한 이메일 차단:
// 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. 장바구니 이탈 복구
유효한 주소에만 이탈 이메일 발송:
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. 주문 위험 평가
사기 감지에 이메일 품질 반영:
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. 고객 세분화
이메일 품질에 따라 고객 세그먼트 생성:
| 세그먼트 | 기준 | 마케팅 전략 |
|---|---|---|
| 고가치 | 검증됨, 비즈니스 이메일 | 프리미엄 캠페인 |
| 표준 | 검증됨, 무료 이메일 | 일반 캠페인 |
| 위험 | 미검증, 오래된 계정 | 재검증 캠페인 |
| 제외 | 무효, 일회용 | 마케팅 제외 |
Metafield 설정
검증 데이터를 저장할 metafield 생성:
고객 Metafields
mutation createMetafieldDefinitions {
metafieldDefinitionCreate(definition: {
namespace: "email_verification"
key: "status"
name: "Email Verification Status"
type: "single_line_text_field"
ownerType: CUSTOMER
}) {
createdDefinition { id }
}
}권장 metafields:
| Namespace | Key | Type | 설명 |
|---|---|---|---|
| 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 | 마지막 검증 날짜 |
| email_verification | disposable | boolean | 일회용 이메일 여부 |
| email_verification | role_based | boolean | 역할 기반 이메일 여부 |
Liquid에서 Metafields 액세스
{% if customer.metafields.email_verification.status == 'valid' %}
<span class="verified-badge">✓ Verified</span>
{% endif %}대량 고객 검증
기존 고객 목록 정리:
고객 내보내기
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;
}대량 검증 및 업데이트
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;
}모범 사례
1. 여러 지점에서 검증
- 등록: 가짜 계정 차단
- 결제: 주문 알림이 고객에게 도달하도록 보장
- 장바구니 이탈: 무효한 주소에 이메일 낭비하지 않음
2. 결과 캐싱
중복 API 호출을 피하기 위해 검증 결과 캐싱:
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. 엣지 케이스 처리
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. 모니터링 및 최적화
다음 메트릭 추적:
- 검증 성공률
- 반송률 감소
- 가짜 계정 방지율
- 장바구니 이탈 이메일 전달률