Cloudflare로 무료 다국어 뉴스레터 시스템 구축: Workers, D1, Resend 및 HMAC 인증
모든 독립 창작자와 블로거가 직면하는 같은 문제가 있습니다: 매달 $20를 써드파티 서비스에 지불하거나 사용자 데이터를 타인에게 맡기지 않으면서 방문자가 당신의 업데이트를 구독하도록 하려면 어떻게 해야 할까요?
블로그에 뉴스레터를 추가하기로 했을 때, 저는 세 가지 양보할 수 없는 요구사항이 있었습니다:
- 비용 제로 — 모든 것이 무료 티어 또는 자체 호스팅
- 완전한 제어 — 써드파티 블랙박스에 의존하지 않음; 구독자 데이터는 자신의 데이터베이스에 저장
- 프로덕션급 보안 — HMAC 서명된 구독 해제 링크로 방문자가 서로를 구독 해제하지 못하도록
- 다국어 지원 — 7개 언어로 현지화된 환영 이메일 및 구독 해제 페이지
저는 이 전체 시스템을 Cloudflare 스택으로 구축했고 정확히 어떻게 하는지 보여드리겠습니다.
아키텍처: 무료 티어, 타협 없음
완전한 기술 스택:
- Cloudflare Workers (
support-api.elvisli.ca): 상태 비저장 백엔드, 구독, 구독 해제, 관리 API 처리 - Cloudflare D1: 구독자 및 지리적 위치 메타데이터를 저장하는 SQLite 데이터베이스
- Cloudflare Pages: 관리 대시보드 및 7개 언어의 구독 해제 페이지용 프론트엔드
- Resend: 트랜잭션 이메일 전송용 무료 API(일일 280개)
- Web Crypto API: 토큰 서명용 브라우저 및 Workers 기본 HMAC-SHA256
총 비용: 월 $0. (Resend 무료 티어를 초과하면 유료 버전으로 업그레이드; 운영 비용 없음)
데이터베이스 설계: 구독자 + 지리적 위치
핵심은 D1의 단일 subscribers 테이블입니다:
CREATE TABLE IF NOT EXISTS subscribers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
source TEXT DEFAULT 'home', -- 구독 소스
lang TEXT, -- 사용자 언어 선택지
country TEXT, region TEXT, city TEXT, -- Cloudflare 자동 지리적 위치
timezone TEXT, -- CF에 의해 자동 채워짐
ip TEXT, user_agent TEXT, -- 감사용
status TEXT DEFAULT 'active', -- 'active' 또는 'unsubscribed'
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_email ON subscribers(email);
CREATE INDEX IF NOT EXISTS idx_status ON subscribers(status);
이 설계의 이유:
- Email UNIQUE: 중복 방지; 재구독은 무음으로 실패하고 가짜 성공 반환(스팸 방지)
- 지리적 위치 열: Cloudflare Workers는 엣지에서
request.cf에 접근 가능하므로 도시/국가/시간대를 무료로 획득 — 써드파티 IP 조회 API 불필요 - Status 필드: 소프트 삭제 패턴; 구독 해제는
status='unsubscribed'를 설정하기만 함, 행은 삭제 안 함(감사 추적 보존) - source 필드: 구독 소스 추적(홈페이지, 이메일 전달 등)
백엔드: Workers API
백엔드는 3개 엔드포인트를 처리하는 단일 Worker 파일:
1. POST /api/subscribe — 새로운 구독
요청 본문:
{
"email": "user@example.com",
"lang": "ko",
"source": "home",
"website": "", // 함정(숨겨진 입력)
"elapsedMs": 5000 -- 양식 로드부터 제출까지의 밀리초
}
다국어 환영 이메일
환영 이메일은 온보딩에 중요하며 다음이 필요합니다:
- 신뢰 구축 — 전문적, 브랜드화, 정직
- 기대 설정 — 무엇을 받을지, 얼마나 자주인지
- 탈출구 제공 — 눈에 띄는 구독 해제 링크
const WELCOME_I18N = {
ko: {
subject: "구독이 완료되었습니다 — elvisli.ca",
heading: "구독 완료 👋",
body: "구독해 주셔서 감사합니다. AI와 직접 만드는 것들, 그리고 가끔의 실험에 대한 새 글을 받은편지함으로 보내드립니다. 스팸은 없습니다.",
cta: "최신 글 읽기 →",
footer: "이 이메일은 해당 주소가 elvisli.ca에서 구독되었기 때문에 발송되었습니다.",
notyou: "직접 구독하지 않으셨나요?",
unsub: "즉시 구독 취소",
},
// ... 다른 언어들
};
라이브 데모: elvisli.ca/ko에서 구독하고 환영 이메일을 확인하세요.

