이 글에서는 이메일 확인 절차까지 갖춘, 사용자 경험이 뛰어난 구독 신청 플로우를 어떻게 정성껏 만들었는지 소개합니다. 여러분도 충분히 따라 하실 수 있습니다.
실제 동작하는 모습이 궁금하다면 지금 바로 victoria.dev에서 제 이메일 목록을 구독해 보셔도 좋습니다.
그럼 지금부터 제가 이 시스템을 어떻게 구축했는지 하나씩 살펴보겠습니다.
Simple Subscribe 소개
메일링 리스트나 뉴스레터를 직접 관리하고 싶다면, Simple Subscribe를 본인의 AWS 리소스에 설치해서 이메일 주소를 수집할 수 있습니다.
Simple Subscribe는 Go 언어로 작성된 오픈소스 API로, AWS Lambda 위에서 실행됩니다. 사이트 방문자가 구독을 신청하면 해당 정보는 DynamoDB 테이블에 저장되어, 언제든 조회하거나 내보낼 수 있는 상태로 관리됩니다.
누군가 구독을 신청하면 구독 확인을 요청하는 이메일이 발송됩니다. 흔히 '더블 옵트인(double opt-in)'이라고 부르지만, 저는 '검증(verified)'이라는 표현을 더 선호합니다.
Simple Subscribe는 서버리스 인프라에서 동작하며, AWS Lambda를 통해 구독, 확인, 구독 해지 요청을 모두 처리합니다.
Simple Subscribe 프로젝트는 GitHub에서 완전한 오픈소스 코드로 공개되어 있습니다. 코드를 열어두고 이 글과 함께 따라가 보시길 권합니다.
이 글에서는 빌드 과정의 각 단계, API의 단일 책임 함수 설계에 담긴 고민, 그리고 이런 AWS 프로젝트에서 반드시 고려해야 할 보안 사항까지 함께 다룹니다.
검증된 구독 플로우를 만드는 방법
검증 없는 이메일 등록 과정은 아주 단순합니다. 누군가 웹사이트 입력란에 이메일을 넣으면 그대로 데이터베이스에 저장됩니다.
하지만 '사용자 입력은 신뢰하지 마라'는 원칙을 알고 있다면, 검증 없는 등록 프로세스라는 생각만으로도 불안해져야 합니다. 스팸은 샌드위치에 구워 먹을 때야 맛있지만, AWS 청구서를 불려가는 순간엔 전혀 즐겁지 않습니다.
CAPTCHA나 퍼즐 같은 방식으로 사람인지 검증할 수도 있지만, 이런 장벽은 잠재적 구독자를 떠나게 만들 정도의 마찰을 일으킬 수 있습니다.
대신 확인 이메일을 활용하면 주소의 정확성과 실제 사람의 의사 표현 두 가지를 모두 확보할 수 있습니다.
이메일 확인이 포함된 구독 플로우를 만들려면, 논리적 단계별로 단일 책임을 가지는 함수들을 설계해야 합니다. 그 단계는 다음과 같습니다.
- 이메일 주소를 받아 기록한다.
- 해당 이메일과 연결된 토큰을 생성해 기록한다.
- 토큰이 포함된 확인 이메일을 해당 주소로 발송한다.
- 이메일 주소와 토큰이 모두 포함된 검증 요청을 받아 처리한다.
Simple Subscribe는 공식 AWS SDK for Go를 사용해 DynamoDB와 SES와 상호작용하며 이 목표들을 구현합니다.
각 단계에서 데이터의 형태와 저장 방식을 고민하세요. '누군가 두 번 구독을 시도하면 어떻게 될까?' 같은 문제 상황이나, '본인 소유가 아닌 이메일로 구독하면 어떻게 대응할까?' 같은 위협 모델링까지 미리 준비하는 데 도움이 됩니다.
준비되셨나요? 이제 각 단계를 하나씩 분해하며 마법이 일어나는 과정을 살펴보겠습니다.
구독 처리
구독 프로세스는 제 사이트 메인 페이지에 있는 것처럼 소박한 웹 폼에서 시작됩니다. type="email" required 속성을 가진 폼 입력 요소는 브라우저 덕분에 기본적인 유효성 검사를 도와줍니다. 폼이 제출되면 Simple Subscribe의 구독 엔드포인트로 GET 요청이 전송됩니다.
Simple Subscribe는 구독자의 이메일이 담긴 쿼리 스트링과 함께 이 엔드포인트로 들어온 GET 요청을 받습니다. 그런 다음 id 값을 생성하고 email과 id를 DynamoDB 테이블에 추가합니다.
이 시점의 테이블 항목은 다음과 같습니다.
| confirm | id | timestamp | |
|---|---|---|---|
subscriber@example.com | false | uuid-xxxxx | 2020-11-01 00:27:39 |
불리언 값을 담는 confirm 컬럼은 해당 항목이 아직 확인되지 않은 구독 요청임을 나타냅니다. 데이터베이스에서 이메일 주소를 검증하려면 올바른 항목을 찾아 confirm 값을 true로 변경해야 합니다.
데이터를 다룰 때는 각 조작의 목적과, 들어오는 요청을 기존 데이터와 어떻게 비교할지 항상 생각해야 합니다.
예를 들어, 같은 이메일 주소로 구독 요청이 한 번 더 들어오면 어떻게 처리할까요?
'새로운 id로 새 행을 추가하면 되지 않을까?'라고 생각할 수 있습니다. 하지만 서버리스 애플리케이션의 데이터베이스 비용이 요청량 기준으로 청구된다면, 이것이 최선의 전략은 아닐 수 있습니다.
DynamoDB 요금은 테이블에서 읽고 쓰는 데이터 양에 따라 결정되므로, 불필요한 데이터가 쌓이지 않도록 하는 것이 유리합니다.
이를 고려하면, 동일한 이메일에 대한 구독 요청은 새 행을 추가하는 대신 업데이트로 처리하는 것이 현명합니다.
실제로 Simple Subscribe는 데이터베이스 항목을 추가하든 수정하든 같은 함수를 사용합니다. 이를 흔히 '업데이트 또는 삽입(update or insert)'이라고 부릅니다.
SQLite 같은 데이터베이스에서는 UPSERT 문법으로 구현하지만, DynamoDB에서는 업데이트 연산을 사용합니다. Go SDK에서는 UpdateItem이 그 문법입니다.
중복 구독 요청이 들어오면 email 값만으로 데이터베이스 항목을 매칭합니다. 기존 항목이 발견되면 해당 항목의 id와 timestamp를 덮어써서 기존 레코드를 갱신함으로써, 중복 요청으로 테이블이 범람하는 것을 막습니다.
이메일 주소 검증 방법
폼을 제출한 후, 구독자는 SES가 보낸 링크가 포함된 이메일을 받게 됩니다. 이 링크는 테이블의 email과 id 값으로 구성되며, 형식은 다음과 같습니다.
<BASE_URL><VERIFY_PATH>/?email=subscriber@example.com&id=uuid-xxxxx여기서 id는 비밀 토큰 역할을 하는 UUID입니다. 충분히 복잡하고 추측하기 어려운 식별자를 제공하여, 본인이 통제하지 않는 이메일 주소로 구독하려는 시도를 막아줍니다.
링크를 클릭하면 쿼리 스트링에 담긴 email과 id가 검증 엔드포인트로 요청을 보냅니다.
이번에는 들어온 email과 id 값을 모두 데이터베이스 레코드와 비교하는 것이 중요합니다. 이를 통해 확인 이메일의 실제 수신자가 요청을 시작했다는 것을 검증할 수 있습니다.
검증 엔드포인트는 이 값들이 데이터베이스의 항목과 일치하는지 확인한 뒤, 또 다른 업데이트 연산을 수행해 confirm을 true로 설정하고 타임스탬프를 갱신합니다. 이후 항목은 다음과 같이 변경됩니다.
| confirm | id | timestamp | |
|---|---|---|---|
subscriber@example.com | true | uuid-xxxxx | 2020-11-01 00:37:39 |
이메일 목록 조회 방법
이제 테이블을 조회해서 이메일 목록을 만들 수 있습니다. 이메일 발송 솔루션에 따라 수동으로 처리하거나, 다른 Lambda 함수를 사용하거나, 심지어 명령줄에서 처리할 수도 있습니다.
요청된 구독(confirm이 false) 데이터가 확인된 구독과 함께 테이블에 저장되어 있으므로, 발송 대상 이메일을 조회할 때 이 데이터를 구분하는 것이 중요합니다. 반드시 confirm이 true인 이메일만 반환하도록 해야 합니다.
구독 해지 링크 제공하기
이메일 검증과 마찬가지로, Simple Subscribe는 email과 id를 인수로 사용해 DynamoDB 테이블에서 항목을 삭제함으로써 구독을 해지합니다.
구독자가 스스로 목록에서 제거될 수 있도록 하려면, 발송하는 모든 이메일에 구독자의 email과 id를 쿼리 스트링으로 포함한 URL을 제공해야 합니다. 형식은 다음과 같습니다.
<BASE_URL><UNSUBSCRIBE_PATH>/?email=subscriber@example.com&id=uuid-xxxxx링크가 클릭되면 쿼리 스트링이 구독 해지 엔드포인트로 전달됩니다. 제공된 email과 id가 데이터베이스 항목과 일치하면 해당 항목이 삭제됩니다.
구독자가 사람의 개입 없이 자동으로 목록에서 탈퇴할 수 있는 방법을 제공하는 것은, 여러분에게 맡겨진 데이터를 윤리적이고 존중하는 태도로 다루겠다는 철학의 일부입니다.
데이터를 돌보는 방법
타인의 데이터를 수락하기로 결정하는 순간, 그 데이터를 돌볼 책임은 여러분에게 있습니다. 이는 여러분이 만드는 모든 것에 적용됩니다. Simple Subscribe의 경우 데이터베이스의 보안 유지와 테이블의 주기적인 정리를 의미합니다.
confirm이 false인 이메일 주소가 특정 기간 이상 남아 있지 않도록, 정기적으로 실행되는 정리(cleanup) 함수를 설정하는 것이 좋습니다. 이 작업은 수동으로 처리하거나, AWS Lambda 함수로 자동화하거나, 명령줄을 통해 수행할 수 있습니다.
정리 작업은 confirm이 false이면서 timestamp가 특정 시점보다 오래된 데이터베이스 항목을 찾아 처리합니다. 사용 사례와 요청량에 따라 정리 주기는 달라질 수 있습니다.
역시 사용 사례에 따라 데이터 백업을 유지하고 싶을 수도 있습니다. 데이터 무결성이 특히 중요하다면 DynamoDB의 온디맨드 백업(On-Demand Backup)이나 특정 시점 복구(Point-in-Time Recovery) 기능을 살펴보세요.
독립적인 구독자 기반을 만들어 보세요
자신만의 구독자 목록을 만드는 일은 무척 보람 있는 도전입니다. 뉴스레터를 시작하거나, 새 콘텐츠 알림을 보내거나, 여러분의 작업을 중심으로 커뮤니티를 만들고 싶다면, 나에게서 당신에게 직접 전달되는 이메일만큼 개인적이고 직접적인 소통 방식은 없습니다.
오늘 바로 Simple Subscribe로 구독자 기반 구축을 시작해 보시길 권합니다. 제 다른 작업들처럼 오픈소스이며 개인 용도로 무료로 사용할 수 있습니다. GitHub 저장소에서 코드를 살펴보거나, SimpleSubscribe.org에서 더 자세히 알아보세요.
이 글이 도움이 되었다면 기쁘겠습니다. victoria.dev에서 저와 함께 배우는 수천 명의 독자들과 함께하세요. RSS로 구독하면 이와 같은 프로젝트 소식을 계속 받아볼 수 있습니다.