이 글은 누구를 위한 것인가
- 장바구니 이탈률을 낮추고 전환을 복구하려는 팀
- 이메일 시퀀스와 쿠폰 자동 발급을 연동하려는 개발자
- 개인화된 이탈 복구 이메일을 구현하려는 팀
들어가며
평균 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시간 이내 첫 이메일의 전환율이 가장 높다. 쿠폰은 마지막 수단으로 아껴야 한다. 처음부터 쿠폰을 주면 고객이 "기다리면 할인해준다"는 패턴을 학습한다. 시퀀스는 구매 완료 즉시 중단해야 이미 구매한 고객에게 재발송하는 실수를 방지한다.