GoとAWSで構築する!メール確認機能付きサーバーレス購読者リストの自作ガイド
この記事では、「イマイチな体験にならない」メール確認機能付きのサブスクリプション登録フローを、丁寧に構築した方法を紹介します。もちろん、あなたも同じことができます。
実際に動いている様子を見たい方は、victoria.devで筆者のメーリングリストに登録してみてください。それでは、具体的な構築手順を見ていきましょう。
Simple Subscribeのご紹介
自分でメーリングリストやニュースレターを運用したい方なら、Simple Subscribeを自身のAWSリソース上にセットアップして、メールアドレスを収集できます。
このオープンソースAPIはGo言語で書かれており、AWS Lambda上で動作します。サイト訪問者がリストに登録すると、その情報はDynamoDBテーブルに保存され、必要に応じてクエリ実行やエクスポートが可能です。
誰かが登録すると、サブスクリプション確認を求めるメールが届きます。これは一般に「ダブルオプトイン」と呼ばれますが、筆者は「検証済み(verified)」という呼び方の方が好みです。
Simple Subscribeはサーバーレスインフラ上で動作し、登録・確認・解除の各リクエストをAWS Lambdaで処理します。
完全にオープンソース化されたSimple SubscribeプロジェクトはGitHubで公開しています。ぜひコードを開いて、この記事を読み進めながら追いかけてみてください。
本記事では、構築の各ステップ、APIを単一責任の関数として設計した考え方、そしてこの種のAWSプロジェクトにおけるセキュリティ上の考慮点について解説します。
検証済みサブスクリプションフローの構築方法
メールアドレスを検証しない登録プロセスはシンプルです。訪問者がサイトのフォームにメールアドレスを入力すると、そのアドレスがデータベースに保存されるだけです。
しかし、「ユーザー入力を信用してはいけない」という教訓を学んだことがあるなら、検証なしの登録プロセスという発想には警戒心が芽生えるはずです。スパムはサンドイッチで焼くなら最高ですが、AWSの請求書を膨らませる分には面白くありません。
CAPTCHAやパズルなどの人間判定を使うこともできますが、これらは摩擦が大きすぎて、せっかくの潜在読者を遠ざけてしまう可能性があります。
そこで代わりに活躍するのが確認メールです。アドレスの正確性と、ユーザーが実際の人間であることの両方を確保できます。
メール確認付きのサブスクリプションフローを構築するには、論理的な各ステップに対応する単一責任の関数を作成します。具体的には以下の4つです。
- メールアドレスを受け取り、記録する
- そのメールアドレスに関連付いたトークンを生成し、記録する
- トークンを含む確認メールをそのアドレス宛てに送信する
- メールアドレスとトークンの両方を含む検証リクエストを受け取る
これらの目標を実現するために、Simple Subscribeは公式のAWS SDK for Goを使用してDynamoDBおよびSESと連携しています。
各段階で、データがどのような形になり、どう保存するのかを考えることが重要です。これにより、「同じ人が2回登録しようとしたらどうする?」といった課題や、「他人のメールアドレスで勝手に登録されたら?」といった脅威モデリングへの対応策が見えてきます。
準備はいいですか?それでは各ステップを分解して、仕組みの詳細を見ていきましょう。
サブスクリプション登録の処理
登録プロセスは、筆者のサイトのトップページにあるような、質素なWebフォームから始まります。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テーブルから項目を削除する関数に渡すことで、メールアドレスを解除(unsubscribe)します。
読者自身がリストから削除できるようにするには、送信する各メールに、解除エンドポイントへのemailとidをクエリ文字列として含むURLを記載する必要があります。形式は次のとおりです。
<BASE_URL><UNSUBSCRIBE_PATH>/?email=subscriber@example.com&id=uuid-xxxxxリンクがクリックされると、クエリ文字列が解除エンドポイントに渡されます。指定されたemailとidがデータベース項目と一致すれば、その項目は削除されます。
人間の介入なしに読者自身が自動的にリストから外せる仕組みを用意することは、信託されたデータを扱ううえでの倫理的で敬意ある姿勢の一環です。
データを大切に守る方法
他人のデータを受け入れると決めた瞬間から、そのデータを大切に管理する責任が生じます。これは構築するすべてのものに当てはまります。Simple Subscribeの場合、データベースのセキュリティ維持と、テーブルの定期的な整理が求められます。
confirmがfalseのまま一定期間を過ぎたメールアドレスを保持し続けないようにするには、定期的に実行されるクリーンアップ関数を設定するのが良いでしょう。これは手動でも、AWS Lambda関数でも、コマンドラインでも実現できます。
クリーンアップでは、confirmがfalseで、かつtimestampが特定の時点より古いデータベース項目を探します。ユースケースやリクエスト量に応じて、クリーンアップの頻度は調整してください。
また、ユースケースによってはデータのバックアップを保持したい場合もあるでしょう。データ整合性を特に重視するなら、DynamoDBのオンデマンドバックアップやポイントインタイムリカバリ(PITR)を検討してみてください。
独立した読者基盤を築こう
自分専用の購読者リストを構築するのは、とてもやりがいのある取り組みです。ニュースレターの開始、新着コンテンツのお知らせ配信、あるいは自分の作品をめぐるコミュニティづくりなど、何よりも個人的で直接的なのは、あなたから読者へ届く一通のメールです。
今日からSimple Subscribeであなたの読者基盤づくりを始めてみてください。筆者の作品の多くと同様にオープンソースで、個人利用は無料です。GitHubリポジトリでコードを覗いてみるか、SimpleSubscribe.orgで詳しく学んでみましょう。
この記事が気に入ったら、ぜひ知らせてください。victoria.devでは数千人の読者と一緒に学んでいます。RSS経由での購読やサイト訪問で、このようなプロジェクトの最新情報をお届けします。
-
EmailJSでVue.jsアプリケーションからメールを送信する方法【初心者向けガイド】
数日前、シンプルなVueプロジェクトに取り組んでいた際、作成したばかりのコンタクトフォームからメールを送信する必要がありました。誰かがフォームに入力するたびに、自動的に通知メールを受け取りたかったのです。 いろいろ調べていく中でEmailJSに出会いました。公式ドキュメントが充実していて、とても使いやすかったため、この記事としてまとめることにしました。この記事がどなたかの参考になれば幸いです :) はじめよう! この記事では、EmailJSを使ってVue.jsアプリケーションからメールを送信する手順を解説します。 先に進む前に、お使いの環境にVue CLIがインストールされていることを前提と
-
GitHub Marketplace入門:開発者向けアプリとツールの公開方法を徹底解説
自作のGitHub Marketplaceアプリで、開発者のワークフローをもっと便利にしたいと考えていませんか?GitHub Marketplaceは、無料・有料の各種ツールを開発者が利用できる公式マーケットプレイスです。プラットフォームでは「Actions」や「Apps」など複数の種類のツールが提供されており、それぞれ登録・公開までの手順が異なります。本記事では、GitHub Marketplaceを使い始めるための基本ステップをわかりやすく解説します。 パブリッシャー確認(Publisher Verification)の申請 まずはパブリッシャー確認の申請から始めましょう。GitHub画