EmailVerify LogoEmailVerify

BigCommerce

Email checker for BigCommerce. Verify customer emails at checkout and registration.

EmailVerify를 BigCommerce와 통합하여 결제 시 고객 이메일을 검증하고, 사기를 줄이고, 주문 확인 및 마케팅 이메일의 전달률을 개선하세요.

통합 방법

방법적합 대상복잡도
Script Manager빠른 설정낮음
Stencil 테마커스텀 테마중간
Storefront API헤드리스 스토어높음

방법 1: Script Manager 통합

BigCommerce Script Manager를 사용하여 실시간 이메일 검증을 추가합니다.

검증 스크립트 생성

  1. StorefrontScript Manager로 이동
  2. Create a Script 클릭
  3. 구성:
    • Name: EmailVerify Email Verification
    • Location: Footer
    • Pages: Checkout

스크립트 코드

<script>
(function() {
    const EMAILVERIFY_API_KEY = 'YOUR_API_KEY';

    // Wait for checkout to load
    function waitForCheckout() {
        const emailInput = document.querySelector('[data-test="customer-email"] input, #email');

        if (emailInput) {
            initVerification(emailInput);
        } else {
            setTimeout(waitForCheckout, 500);
        }
    }

    function initVerification(emailInput) {
        const statusEl = document.createElement('span');
        statusEl.className = 'bv-status';
        emailInput.parentNode.appendChild(statusEl);

        let verifyTimeout;
        let lastEmail = '';

        emailInput.addEventListener('blur', function() {
            const email = this.value;

            if (!email || email === lastEmail || !isValidFormat(email)) {
                return;
            }

            clearTimeout(verifyTimeout);

            verifyTimeout = setTimeout(function() {
                verifyEmail(email, statusEl);
                lastEmail = email;
            }, 500);
        });
    }

    async function verifyEmail(email, statusEl) {
        statusEl.textContent = 'Verifying...';
        statusEl.className = 'bv-status verifying';

        try {
            const response = await fetch('https://api.emailverify.ai/v1/verify', {
                method: 'POST',
                headers: {
                    'Authorization': 'Bearer ' + EMAILVERIFY_API_KEY,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ email: email })
            });

            const data = await response.json();

            if (data.status === 'valid') {
                statusEl.textContent = '✓ Email verified';
                statusEl.className = 'bv-status valid';
            } else if (data.status === 'invalid') {
                statusEl.textContent = '✗ Please enter a valid email';
                statusEl.className = 'bv-status invalid';
            } else if (data.result && data.result.disposable) {
                statusEl.textContent = '✗ Please use a permanent email';
                statusEl.className = 'bv-status invalid';
            } else {
                statusEl.textContent = '';
                statusEl.className = 'bv-status';
            }
        } catch (error) {
            console.error('Verification error:', error);
            statusEl.textContent = '';
            statusEl.className = 'bv-status';
        }
    }

    function isValidFormat(email) {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    }

    // Add styles
    const style = document.createElement('style');
    style.textContent = `
        .bv-status {
            display: block;
            margin-top: 4px;
            font-size: 12px;
        }
        .bv-status.verifying { color: #666; }
        .bv-status.valid { color: #28a745; }
        .bv-status.invalid { color: #dc3545; }
    `;
    document.head.appendChild(style);

    // Initialize
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', waitForCheckout);
    } else {
        waitForCheckout();
    }
})();
</script>

방법 2: Stencil 테마 통합

커스텀 Stencil 테마의 경우 테마 파일에 직접 검증을 추가합니다.

테마 컴포넌트 생성

// assets/js/theme/global/email-verification.js
import utils from '@bigcommerce/stencil-utils';

export default class EmailVerification {
    constructor() {
        this.apiKey = window.emailverifyConfig?.apiKey;
        this.emailInputs = document.querySelectorAll('input[type="email"]');
        this.cache = new Map();

        if (this.apiKey && this.emailInputs.length) {
            this.init();
        }
    }

    init() {
        this.emailInputs.forEach(input => {
            this.attachVerification(input);
        });
    }

    attachVerification(input) {
        const statusEl = document.createElement('span');
        statusEl.className = 'form-field-verification';
        input.parentNode.appendChild(statusEl);

        let timeout;

        input.addEventListener('blur', () => {
            const email = input.value.trim();

            if (!email || !this.isValidFormat(email)) {
                this.clearStatus(statusEl);
                return;
            }

            clearTimeout(timeout);
            timeout = setTimeout(() => this.verify(email, statusEl, input), 500);
        });
    }

    async verify(email, statusEl, input) {
        // Check cache
        if (this.cache.has(email)) {
            this.displayResult(this.cache.get(email), statusEl, input);
            return;
        }

        this.showLoading(statusEl);

        try {
            const response = await fetch('https://api.emailverify.ai/v1/verify', {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${this.apiKey}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ email })
            });

            const data = await response.json();
            this.cache.set(email, data);
            this.displayResult(data, statusEl, input);

        } catch (error) {
            console.error('Email verification error:', error);
            this.clearStatus(statusEl);
        }
    }

    displayResult(data, statusEl, input) {
        input.classList.remove('is-valid', 'is-invalid');

        if (data.status === 'valid' && !data.result?.disposable) {
            statusEl.innerHTML = '<span class="icon icon--success"></span> Verified';
            statusEl.className = 'form-field-verification is-valid';
            input.classList.add('is-valid');
        } else if (data.status === 'invalid') {
            statusEl.innerHTML = '<span class="icon icon--error"></span> Invalid email';
            statusEl.className = 'form-field-verification is-invalid';
            input.classList.add('is-invalid');
        } else if (data.result?.disposable) {
            statusEl.innerHTML = '<span class="icon icon--warning"></span> Please use a permanent email';
            statusEl.className = 'form-field-verification is-invalid';
            input.classList.add('is-invalid');
        } else {
            this.clearStatus(statusEl);
        }
    }

    showLoading(statusEl) {
        statusEl.innerHTML = '<span class="spinner"></span> Verifying...';
        statusEl.className = 'form-field-verification is-loading';
    }

    clearStatus(statusEl) {
        statusEl.innerHTML = '';
        statusEl.className = 'form-field-verification';
    }

    isValidFormat(email) {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    }
}

테마에서 초기화

// assets/js/theme/global.js
import EmailVerification from './global/email-verification';

export default class Global extends PageManager {
    onReady() {
        // ... other initializations

        new EmailVerification();
    }
}

테마 구성

config.json에 추가:

{
    "settings": {
        "emailverify_enabled": true,
        "emailverify_api_key": ""
    }
}

스키마 설정

관리자 구성을 위해 schema.json에 추가:

[
    {
        "name": "EmailVerify",
        "settings": [
            {
                "type": "checkbox",
                "label": "Enable Email Verification",
                "id": "emailverify_enabled"
            },
            {
                "type": "text",
                "label": "API Key",
                "id": "emailverify_api_key"
            }
        ]
    }
]

템플릿에 Config 주입

{{!-- templates/layout/base.html --}}
<script>
    window.emailverifyConfig = {
        enabled: {{settings.emailverify_enabled}},
        apiKey: '{{settings.emailverify_api_key}}'
    };
</script>

방법 3: Storefront API 통합

Storefront API를 사용하는 헤드리스 BigCommerce 스토어용.

검증 서비스

// services/email-verification.ts
interface VerificationResult {
  email: string;
  status: 'valid' | 'invalid' | 'unknown';
  score?: number;
  isDisposable: boolean;
  isRole: boolean;
}

class EmailVerificationService {
  private apiKey: string;
  private cache: Map<string, VerificationResult> = new Map();

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  async verify(email: string): Promise<VerificationResult> {
    // Check cache
    const cached = this.cache.get(email);
    if (cached) {
      return cached;
    }

    try {
      const response = await fetch('https://api.emailverify.ai/v1/verify', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ email }),
      });

      const data = await response.json();

      const result: VerificationResult = {
        email,
        status: data.status,
        score: data.score,
        isDisposable: data.result?.disposable ?? false,
        isRole: data.result?.role ?? false,
      };

      this.cache.set(email, result);
      return result;

    } catch (error) {
      console.error('Verification error:', error);
      return {
        email,
        status: 'unknown',
        isDisposable: false,
        isRole: false,
      };
    }
  }

  isValid(result: VerificationResult): boolean {
    return result.status === 'valid' && !result.isDisposable;
  }

  getErrorMessage(result: VerificationResult): string | null {
    if (result.status === 'invalid') {
      return 'Please enter a valid email address.';
    }
    if (result.isDisposable) {
      return 'Temporary email addresses are not accepted.';
    }
    return null;
  }
}

export const emailVerification = new EmailVerificationService(
  process.env.EMAILVERIFY_API_KEY!
);

React 결제 컴포넌트

// components/checkout/EmailInput.tsx
import { useState, useCallback } from 'react';
import { emailVerification } from '@/services/email-verification';
import { debounce } from 'lodash';

interface EmailInputProps {
  value: string;
  onChange: (value: string) => void;
  onValidationChange: (isValid: boolean) => void;
}

export function EmailInput({ value, onChange, onValidationChange }: EmailInputProps) {
  const [status, setStatus] = useState<'idle' | 'verifying' | 'valid' | 'invalid'>('idle');
  const [message, setMessage] = useState('');

  const verifyEmail = useCallback(
    debounce(async (email: string) => {
      if (!email || !email.includes('@')) {
        setStatus('idle');
        setMessage('');
        return;
      }

      setStatus('verifying');

      const result = await emailVerification.verify(email);

      if (emailVerification.isValid(result)) {
        setStatus('valid');
        setMessage('Email verified');
        onValidationChange(true);
      } else {
        setStatus('invalid');
        setMessage(emailVerification.getErrorMessage(result) || 'Verification failed');
        onValidationChange(false);
      }
    }, 500),
    [onValidationChange]
  );

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const newValue = e.target.value;
    onChange(newValue);
    verifyEmail(newValue);
  };

  return (
    <div className="form-field">
      <label htmlFor="email">Email Address</label>
      <input
        type="email"
        id="email"
        value={value}
        onChange={handleChange}
        className={`form-input ${status === 'valid' ? 'is-valid' : ''} ${status === 'invalid' ? 'is-invalid' : ''}`}
      />
      {status !== 'idle' && (
        <span className={`form-feedback ${status}`}>
          {status === 'verifying' && <Spinner />}
          {message}
        </span>
      )}
    </div>
  );
}

Next.js API 라우트

API 키를 보호하기 위한 서버 측 검증:

// pages/api/verify-email.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const { email } = req.body;

  if (!email) {
    return res.status(400).json({ error: 'Email required' });
  }

  try {
    const response = await fetch('https://api.emailverify.ai/v1/verify', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.EMAILVERIFY_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ email }),
    });

    const data = await response.json();

    return res.status(200).json({
      valid: data.status === 'valid' && !data.result?.disposable,
      status: data.status,
      disposable: data.result?.disposable ?? false,
    });

  } catch (error) {
    console.error('Verification error:', error);
    return res.status(200).json({ valid: true }); // Allow on error
  }
}

웹훅 통합

주문을 처리하고 이메일을 비동기적으로 검증합니다.

웹훅 핸들러

// api/webhooks/bigcommerce.js
import crypto from 'crypto';
import EmailVerify from '@emailverify/sdk';

const bv = new EmailVerify(process.env.EMAILVERIFY_API_KEY);

export default async function handler(req, res) {
    // Verify webhook signature
    const signature = req.headers['x-bc-webhook-signature'];
    const expectedSignature = crypto
        .createHmac('sha256', process.env.BC_WEBHOOK_SECRET)
        .update(JSON.stringify(req.body))
        .digest('base64');

    if (signature !== expectedSignature) {
        return res.status(401).json({ error: 'Invalid signature' });
    }

    const { scope, data } = req.body;

    if (scope === 'store/order/created') {
        await handleOrderCreated(data);
    }

    res.status(200).json({ received: true });
}

async function handleOrderCreated(data) {
    const orderId = data.id;

    // Fetch order details from BigCommerce
    const order = await fetchOrder(orderId);

    if (!order.billing_address?.email) {
        return;
    }

    // Verify email
    const result = await bv.verify({ email: order.billing_address.email });

    // Store verification result
    await storeVerificationResult(orderId, result);

    // Flag suspicious orders
    if (result.status === 'invalid' || result.is_disposable) {
        await flagOrder(orderId, {
            reason: result.status === 'invalid'
                ? 'Invalid email address'
                : 'Disposable email detected',
            verification: result,
        });
    }
}

async function fetchOrder(orderId) {
    const response = await fetch(
        `https://api.bigcommerce.com/stores/${process.env.BC_STORE_HASH}/v2/orders/${orderId}`,
        {
            headers: {
                'X-Auth-Token': process.env.BC_ACCESS_TOKEN,
                'Accept': 'application/json',
            },
        }
    );

    return response.json();
}

async function flagOrder(orderId, data) {
    // Add order note or update status
    await fetch(
        `https://api.bigcommerce.com/stores/${process.env.BC_STORE_HASH}/v2/orders/${orderId}`,
        {
            method: 'PUT',
            headers: {
                'X-Auth-Token': process.env.BC_ACCESS_TOKEN,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                staff_notes: `⚠️ Email Verification Warning: ${data.reason}`,
            }),
        }
    );
}

웹훅 등록

async function registerWebhook() {
    const response = await fetch(
        `https://api.bigcommerce.com/stores/${process.env.BC_STORE_HASH}/v3/hooks`,
        {
            method: 'POST',
            headers: {
                'X-Auth-Token': process.env.BC_ACCESS_TOKEN,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                scope: 'store/order/created',
                destination: 'https://yoursite.com/api/webhooks/bigcommerce',
                is_active: true,
            }),
        }
    );

    return response.json();
}

대량 고객 검증

스토어의 기존 고객을 검증합니다.

import EmailVerify from '@emailverify/sdk';

const bv = new EmailVerify(process.env.EMAILVERIFY_API_KEY);

async function verifyAllCustomers() {
    const customers = await fetchAllCustomers();
    const emails = customers.map(c => c.email);

    console.log(`Verifying ${emails.length} customer emails...`);

    // Submit bulk job
    const job = await bv.verifyBulk(emails);
    console.log(`Job ID: ${job.job_id}`);

    // Wait for completion
    let status;
    do {
        await sleep(5000);
        status = await bv.getBulkJobStatus(job.job_id);
        console.log(`Progress: ${status.progress_percent}%`);
    } while (status.status !== 'completed');

    // Process results
    const results = await bv.getBulkJobResults(job.job_id);

    const stats = { valid: 0, invalid: 0, disposable: 0 };

    for (const result of results.results) {
        if (result.status === 'valid') stats.valid++;
        else stats.invalid++;

        if (result.is_disposable) stats.disposable++;

        // Update customer record
        const customer = customers.find(c => c.email === result.email);
        if (customer) {
            await updateCustomerMeta(customer.id, {
                email_status: result.status,
                email_score: result.score,
            });
        }
    }

    console.log('Verification complete:', stats);
    return stats;
}

async function fetchAllCustomers() {
    const customers = [];
    let page = 1;

    while (true) {
        const response = await fetch(
            `https://api.bigcommerce.com/stores/${process.env.BC_STORE_HASH}/v3/customers?page=${page}&limit=250`,
            {
                headers: {
                    'X-Auth-Token': process.env.BC_ACCESS_TOKEN,
                },
            }
        );

        const data = await response.json();

        if (data.data.length === 0) break;

        customers.push(...data.data);
        page++;
    }

    return customers;
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

모범 사례

1. API 키 보호

클라이언트 측 코드에서 API 키를 노출하지 마세요. 프록시를 사용하세요:

// Server-side proxy
app.post('/api/verify', async (req, res) => {
    const { email } = req.body;

    const result = await bv.verify({ email });

    // Only return necessary data
    res.json({
        valid: result.status === 'valid',
        disposable: result.is_disposable,
    });
});

2. 결과 캐싱

const cache = new Map();
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours

function getCachedResult(email) {
    const cached = cache.get(email);
    if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
        return cached.result;
    }
    return null;
}

function setCachedResult(email, result) {
    cache.set(email, { result, timestamp: Date.now() });
}

3. 오류를 우아하게 처리

try {
    const result = await verifyEmail(email);
    // ... handle result
} catch (error) {
    console.error('Verification failed:', error);
    // Allow checkout to proceed
    return { valid: true };
}

관련 리소스

On this page