장바구니 이탈 복구: 자동화 이메일 시퀀스 설계

이커머스

장바구니 이탈이메일 자동화전환율 최적화이커머스마케팅 자동화

이 글은 누구를 위한 것인가

  • 장바구니 이탈률을 낮추고 전환을 복구하려는 팀
  • 이메일 시퀀스와 쿠폰 자동 발급을 연동하려는 개발자
  • 개인화된 이탈 복구 이메일을 구현하려는 팀

들어가며

평균 70%의 사용자가 장바구니를 채우고 떠난다. 이탈 후 1시간 이내 이메일을 보내면 15~20%가 구매를 완료한다. 타이밍과 개인화가 핵심이다.

이 글은 bluefoxdev.kr의 장바구니 이탈 복구 가이드 를 참고하여 작성했습니다.


1. 이탈 복구 전략

[이탈 감지 기준]
  장바구니에 상품 추가 후:
  - 30분간 미결제 → 이탈로 판단
  - 세션 종료 또는 페이지 이탈
  - 단, 체크아웃 페이지 진입 후 이탈은 별도 처리

[3단계 이메일 시퀀스]
  Email 1 (1시간 후): 부드러운 리마인더
    - "장바구니에 담아두신 상품이 있어요"
    - 상품 이미지 + 가격
    - CTA: "장바구니 보기"
    - 쿠폰 없음

  Email 2 (24시간 후): 재고 압박 + 사회적 증거
    - "다른 분들이 관심 갖고 있어요"
    - 재고 수량 표시 (5개 이하 시)
    - 관련 상품 추천 3개
    - 쿠폰 없음

  Email 3 (72시간 후): 마지막 인센티브
    - "특별 할인 코드 드립니다"
    - 10% 쿠폰 자동 발급 (1회용)
    - 24시간 만료
    - 강한 CTA

[발송 조건]
  - 이메일 수신 동의한 사용자만
  - 이미 구매 완료한 경우 발송 중단
  - 이전 이메일 열람 여부에 따라 다음 단계 조정

2. 이탈 복구 구현

// 장바구니 이탈 감지 및 이메일 큐 등록
class CartAbandonmentService {
  async detectAbandonment(userId: string) {
    const cart = await db.cart.findFirst({
      where: { userId, status: 'ACTIVE' },
      include: { items: { include: { product: true } } },
    });

    if (!cart || cart.items.length === 0) return;

    const minutesSinceUpdate = (Date.now() - new Date(cart.updatedAt).getTime()) / 60000;
    if (minutesSinceUpdate < 30) return;

    // 이미 이탈 복구 시퀀스 실행 중인지 확인
    const existing = await db.abandonmentSequence.findFirst({
      where: { cartId: cart.id, status: 'ACTIVE' },
    });
    if (existing) return;

    // 시퀀스 시작
    const sequence = await db.abandonmentSequence.create({
      data: { userId, cartId: cart.id, status: 'ACTIVE', currentStep: 0 },
    });

    // 1시간 후 첫 이메일 스케줄
    await emailQueue.schedule({
      type: 'CART_ABANDONMENT',
      step: 1,
      sequenceId: sequence.id,
      sendAt: new Date(Date.now() + 60 * 60000),
    });
  }

  async sendAbandonmentEmail(sequenceId: string, step: number) {
    const sequence = await db.abandonmentSequence.findUnique({
      where: { id: sequenceId },
      include: { user: true, cart: { include: { items: { include: { product: true } } } } },
    });

    if (!sequence || sequence.status !== 'ACTIVE') return;

    // 구매 완료 시 중단
    const purchased = await db.order.findFirst({
      where: { userId: sequence.userId, createdAt: { gte: sequence.createdAt } },
    });
    if (purchased) {
      await db.abandonmentSequence.update({ where: { id: sequenceId }, data: { status: 'CONVERTED' } });
      return;
    }

    let couponCode: string | undefined;
    if (step === 3) {
      // 3단계: 쿠폰 자동 발급
      const coupon = await db.coupon.create({
        data: {
          code: `CART${Date.now()}`,
          type: 'PERCENTAGE',
          value: 10,
          maxUses: 1,
          userId: sequence.userId, // 해당 사용자 전용
          expiresAt: new Date(Date.now() + 24 * 60 * 60000),
        },
      });
      couponCode = coupon.code;
    }

    await sendEmail({
      to: sequence.user.email,
      template: `cart_abandonment_step${step}`,
      data: {
        userName: sequence.user.name,
        cartItems: sequence.cart.items,
        cartUrl: `${process.env.APP_URL}/cart?token=${sequence.id}`,
        couponCode,
      },
    });

    // 다음 단계 스케줄
    const nextStepDelays = [0, 24 * 60, 72 * 60]; // 분 단위
    if (step < 3) {
      await emailQueue.schedule({
        type: 'CART_ABANDONMENT',
        step: step + 1,
        sequenceId,
        sendAt: new Date(Date.now() + nextStepDelays[step] * 60000),
      });
    }

    await db.abandonmentSequence.update({
      where: { id: sequenceId },
      data: { currentStep: step, lastEmailSentAt: new Date() },
    });
  }
}

async function sendEmail(params: any) {}
const emailQueue = { schedule: async (params: any) => {} };
const db = {} as any;

마무리

장바구니 이탈 복구의 핵심은 타이밍이다. 1시간 이내 첫 이메일의 전환율이 가장 높다. 쿠폰은 마지막 수단으로 아껴야 한다. 처음부터 쿠폰을 주면 고객이 "기다리면 할인해준다"는 패턴을 학습한다. 시퀀스는 구매 완료 즉시 중단해야 이미 구매한 고객에게 재발송하는 실수를 방지한다.