이 튜토리얼에서는 일반적인 예약 워크플로우를 설정합니다:
- 사용자가 예약을 완료한 후 예약 확인 이메일을 전송합니다.
- 사용자가 예약을 시작했지만 시간 내에 완료하지 않으면 복구 이메일을 전송합니다.
완료되면 다음을 갖게 됩니다:
- 두 개의 커스텀 이벤트 (
booking_started, booking_complete)
- 완료 대 방치에 따라 분기하는 하나의 Journey
- 확인 세부 정보를 위한 예약 Data Feed
- 복구 인센티브를 위한 선택적 쿠폰 Data Feed
이 가이드는 OneSignal 구성에 중점을 둡니다. 예약 시스템과 백엔드는 모든 언어나 프레임워크로 구현할 수 있습니다.
설정 플로우
- 앱이
booking_started 커스텀 이벤트를 추적합니다.
- 이것이 사용자를 Journey에 진입시킵니다.
- Journey는
booking_complete 이벤트를 기다리고, 시간 내에 수신되지 않으면 후속 리마인더를 전송합니다.
- 예약이 완료되면, OneSignal은 전송 시 예약 Data Feed를 호출하고 최신 예약 세부 정보가 포함된 확인 이메일을 전송합니다.
- 예약이 대기 시간 내에 완료되지 않으면, Journey는 만료 경로를 따라 복구 이메일을 전송합니다.
전제 조건
시작하기 전에 다음이 있는지 확인하세요:
- 이메일 채널이 활성화된 OneSignal 앱
- 예약 및/또는 쿠폰 데이터를 JSON으로 반환할 수 있는 백엔드 엔드포인트
- 앱, 백엔드 및 OneSignal 외부 ID 간에 공유되는 안정적인 사용자 식별자
- 커스텀 이벤트에 대한 액세스
1. 예약 이벤트 추적
다음 커스텀 이벤트를 추적하세요. 이들은 앱에서 (SDK 사용) 또는 백엔드에서 (REST API 사용) 올 수 있습니다.
이벤트 이름:
booking_started — 사용자가 예약 플로우를 시작할 때
booking_complete — 예약이 성공적으로 완료될 때
모바일 SDK 및/또는 웹 SDK의 trackEvent() 메서드를 사용하여 앱/웹사이트에서 직접 커스텀 이벤트를 전송하세요.OneSignal.User.trackEvent("booking_started");
OneSignal.User.trackEvent("booking_complete");
백엔드에서 이벤트를 추적하는 경우, 커스텀 이벤트 생성 API를 사용하여 OneSignal로 이벤트를 전송하세요.curl --request POST \
--url https://api.onesignal.com/apps/{app_id}/custom_events \
--header 'Authorization: Key YOUR_APP_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"events": [
{
"name": "booking_started",
"external_id": "user123",
"properties": {}
}
]
}'
이벤트 추적 시와 백엔드에서 데이터를 반환할 때 동일한 사용자 ID를 사용하세요. 일치하지 않는 ID는 개인화가 누락되는 가장 일반적인 원인입니다.
2. Data Feed 별칭 생성
OneSignal에서 설정 > Data Feeds로 이동하여 다음 별칭을 생성하세요.
예약 Data Feed:
이 피드를 사용하여 전송 시 최신 예약 세부 정보를 가져옵니다.
- 별칭:
booking_data
- 메서드: GET
- URL:
https://your-domain.com/datafeed/booking?user_id={{subscription.external_id}}
응답 예시:
{
"first_name": "Sam",
"last_booking": {
"service_type": "상담",
"booking_date": "2026년 1월 22일",
"booking_time": "오후 2:00",
"price": 45
}
}
쿠폰 Data Feed (선택사항):
복구 이메일에 쿠폰 코드를 포함하려면 이 선택적 피드를 사용하세요.
https://your-domain.com/datafeed/coupon?user_id={{subscription.external_id}}
응답 예시:
{
"first_name": "Sam",
"code": "PROMO8F3K2",
"discount_text": "10%",
"expires_in_hours": 2,
"deep_link": "https://your-domain.com/checkout?coupon=PROMO8F3K2"
}
Data Feed 엔드포인트를 보호하세요. 프로덕션에서는 요청 헤더에 API 키(예: x-api-key)를 전송하고 URL에 비밀을 포함하는 대신 설정 > Data Feeds에서 해당 헤더를 구성하세요.
3. 이메일 템플릿 생성
예약 확인 이메일:
제목:
본문:
{{ data_feed.booking_data.first_name | default: "고객님" }}, 안녕하세요
예약해 주셔서 감사합니다! 예약 세부 정보는 다음과 같습니다:
서비스: {{ data_feed.booking_data.last_booking.service_type }}
날짜: {{ data_feed.booking_data.last_booking.booking_date }}
시간: {{ data_feed.booking_data.last_booking.booking_time }}
가격: {{ data_feed.booking_data.last_booking.price }}원
만나 뵙기를 기대하겠습니다!
예약 복구 이메일
제목:
본문:
쿠폰 Data Feed 사용
쿠폰 Data Feed 미사용
{{ data_feed.coupon.first_name | default: "고객님" }}, 안녕하세요
앞으로 {{ data_feed.coupon.expires_in_hours }}시간 내에 예약을 완료하시면
이 코드로 {{ data_feed.coupon.discount_text }} 할인을 받으실 수 있습니다:
{{ data_feed.coupon.code }}
여기서 사용하세요:
{{ data_feed.coupon.deep_link }}
안녕하세요,
아직 예약을 완료하지 않으셨습니다!
지금 완료하여 다음 예약에서 할인을 받으세요.
이 링크를 사용하여 예약을 완료하세요:
[여기에 딥링크 삽입]
Data Feed 필드가 누락된 경우 빈 콘텐츠를 방지하기 위해 Liquid에 항상 default 필터를 포함하세요.
4. Journey 구축
-
OneSignal에서 메시지 > Journeys > Journey 생성으로 이동
-
입장 트리거를 다음으로 설정:
-
다음까지 대기 단계 추가:
- 조건: 커스텀 이벤트 발생
- 이벤트 이름:
booking_complete
- 최대 대기 시간: 10분
- 만료 경로 활성화
-
분기 구성:
- 완료됨: 예약 확인 이메일 전송
- 만료됨: 복구 이메일 전송
만료 분기를 통해 앱에서 추가 로직 없이 방치를 처리할 수 있습니다. 참조:
5. 테스트 및 확인
이벤트 확인
앱이나 백엔드에서 커스텀 이벤트를 트리거하고 확인하세요.
OneSignal에서 분석 > 커스텀 이벤트로 이동하여 다음이 표시되는지 확인:
- 외부 ID에 대해
booking_started 이벤트가 나타남
- 외부 ID에 대해
booking_complete 이벤트가 나타남
Data Feeds 확인
알려진 사용자 ID를 사용하여 Data Feed 엔드포인트를 수동으로 호출하고 확인:
- 200 응답이 반환됨
- 모든 예상 필드가 존재함
이메일 확인
Journey 편집기에서 테스트 메시지를 전송하고 확인:
- 예약 이메일에 실제 예약 세부 정보가 포함됨
- 복구 이메일에 유효한 쿠폰이 포함됨
- Liquid 변수가 비어있게 렌더링되지 않음
개인화가 누락된 경우, Data Feed 요청의 사용자 ID가 Journey를 트리거한 사용자와 일치하는지 확인하세요.
예시: Data Feed 구현
Node.js Data Feed 엔드포인트 예시
이 예시는 예약 확인 및 복구 Data Feed를 위한 최소 Express 구현을 보여줍니다. JSON 응답 형태가 이메일 템플릿과 일치하는 한 백엔드 언어, 프레임워크 및 데이터 소스는 다를 수 있습니다.예약 Data Feed 예시
import express from "express";
const app = express();
function dataFeedAuth(req, res, next) {
if (req.headers["x-api-key"] !== process.env.DATAFEED_API_KEY) {
return res.status(401).json({ error: "Unauthorized" });
}
next();
}
app.get("/datafeed/booking", dataFeedAuth, async (req, res) => {
const { user_id } = req.query;
if (!user_id) {
return res.status(400).json({ error: "Missing user_id" });
}
const booking = await getLatestBookingForUser(user_id);
if (!booking) {
return res.status(404).json({ error: "No booking found" });
}
res.json({
first_name: booking.first_name,
last_booking: {
service_type: booking.service_type,
booking_date: booking.booking_date,
booking_time: booking.booking_time,
price: booking.price
}
});
});
쿠폰 Data Feed 예시
app.get("/datafeed/coupon", dataFeedAuth, async (req, res) => {
const { user_id } = req.query;
if (!user_id) {
return res.status(400).json({ error: "Missing user_id" });
}
const coupon = await generateCouponForUser(user_id);
res.json({
first_name: coupon.first_name,
code: coupon.code,
discount_text: coupon.discount_text,
expires_in_hours: coupon.expires_in_hours,
deep_link: coupon.deep_link
});
});
구현 가이드라인
- 응답을 빠르게 유지 (Data Feeds는 전송 시 호출됨)
- 항상 예측 가능한 JSON 구조 반환
- 데이터가 존재하지 않을 때 404 사용
- 요청 헤더를 통해 전송된 API 키로 엔드포인트 보안
일반적인 문제
이메일에 빈 값 표시
- Data Feed가 404 반환
- JSON 응답에서 필드 이름 변경
- 사용자 식별 불일치
Journey가 분기하지 않음
booking_complete 이벤트가 추적되지 않음
- 이벤트 이름 불일치 (대소문자 구분)
- 대기 시간 외부에서 이벤트 발생
Data Feed가 401 또는 403 반환
- API 키 누락 또는 무효
- Data Feed 설정에서 헤더가 구성되지 않음
다음 단계
- 더 고급 Journey 조건을 위한 이벤트 속성(서비스 유형, 가격) 추가
- 푸시 또는 SMS 리마인더와 같은 추가 복구 단계 추가
- 반복되는 복구 메시지를 방지하기 위해 Journey 종료 규칙 사용