ニュースレターを自前で運用してみた
このブログでは何年もニュースレターを運用してきましたが、長いこと一通も配信しない時期が続きました。ようやく再開するまでの経緯と、その過程で学んだことをお話しします。
最初に一言断っておきます。というのも、ここで誤解が生じたことがあるからです。「自前でホストする」と言っても、ニュースレターのプラットフォームを使わないという意味です。登録用のバックエンドや配信に使う CLI は自作で、記事自体は git リポジトリの中にあるただの Markdown ファイルです。配信のバックエンドとしては今もPlunkを使っています(なので SES やバウンス、サプレッションリスト、配信停止ページの面倒は見なくて済みます)。Plunk 自体はオープンソースなので自前でホストすることもできますが、到達性まわりは例外対応が多く、そこはお金を払って誰かに運用してもらった方が気が楽です。🙃
Tinyletter 時代

出典: Wayback Machine
長年、私の構成はとてもシンプルでした。サイトに小さなフォームを置き、送信先はTinyletterというライター向けの小さなニュースレターサービスです。気に入っていたのは、そのシンプルさでした。メールの到達性やバウンス率、サプレッションリスト、SPF、DKIM、DMARCといったことを一切考える必要がありませんでした。文章を書いて送信ボタンを押せば、読者に届く。それだけでした。

とにかく動いていました。そして Tinyletter はサービスを終了しました。
少し歴史を振り返っておきます。Tinyletter は 2010年にPhilip Kaplan氏によって作られ、2010年10月31日、日曜日たった一日でコーディングされたと言われています。
1年後に Mailchimp に買収され、ファネルやセグメント、A/B テストのことを考えずに個人的なニュースレターを書きたい人たちにとって、静かにデファクトスタンダードな居場所になっていきました。
そして2023年後半、Mailchimp(現在は Intuit 傘下)はサービス終了を発表しました。公式の表現では「事業の優先順位が変化した」ため「マーケターや中小企業の成長を支援するツール開発に注力する」とのことでした。ライターはもともと彼らの主要顧客ではなかったのです。

出典: EmailOctopus
Tinyletter が 2024年2月29日にサービスを終了する直前に、登録者リストの最終バックアップは取りました。ただ、その後どうするかは何も決めていませんでした。
否認
この時点で、私はサードパーティのサービスを使うこと自体に後ろ向きになっていました。同じことがまた繰り返されるかもしれないと思ったからです。
それでも一通り選択肢は見てみましたが、どれもしっくりきませんでした。
- 高すぎる! ほとんどのサービスはコンタクト数で課金し、ビジネス向けのファネルを回すことを前提にしています。人に手紙を書くためではありません。
- マーケティング寄りすぎる! テンプレート、ドラッグ&ドロップのビルダー、A/B テスト、エンゲージメントスコア、トラッキングピクセル。そもそも言葉遣いが違います。キャンペーンを回したいのではなく、ただメールを送りたいのです!
- ハッカー向けでない。 Markdown も CLI も、使っていて楽しい API もありません。すべてがマーケティングチーム向けのウェブダッシュボードの中で完結します。
- オープンソースでない。 次の Tinyletter が終了しても、また移行せずに続けられるようにしたいのです。
- デフォルトでトラッキング! 開封トラッキング、クリックトラッキング、フッターに仕込まれるピクセル。誰が何を開いたかなんて知りたくありません。私が書いて、あなたが読む(あるいは読まない)。それで終わりです。
Fly.io への移行
いつニュースレターが復活するのかと何度も聞かれたので、fly.io上に急ごしらえの仕組みを作りました。小さな Rust 製 API と、登録者を入れた CSV ファイル、そしてサイトから登録できる導線です。配信は後でなんとかするとして、まずは登録だけでも受け付けられるようにしておこうという考えでした。
そしてリストはそのまま放置されました。
実は、長い間放置されたリスト(コールドリスト)自体が問題になります。久しぶりにそうしたリストに配信すると、メールプロバイダーに不審に思われ、スパム判定されることがあるのです。自分のニュースレターが突然自分に牙をむくこともあるわけです。
配信サービスの探索
ここが断トツで一番大変なところでした。Resend、Postmark、SendGrid、Mailgun、Amazon SESなど、ほかにもたくさん調べました。しかしどれも、小規模なニュースレターには高すぎる、API がひどい、GDPR に準拠していない、あるいは複雑すぎる、といった問題がありました。
諦めかけたときに見つけたのがPlunkでした。オープンソースで、料金はリストの規模に応じて変動し、API も扱いやすい。考えたくない到達性まわりの作業(SES 連携、バウンス処理、サプレッションリスト、配信停止ページのホスティング)をやってくれます。今は有料ユーザーとして使っています。別に関係者ではなく、単純に満足している一ユーザーです。
小さなコントリビューションを送ってみたところ、10分でマージされました。コミュニティの一員になれたような気がして、嬉しくなりました。
久しぶりに1000件以上のコンタクトへ、初の正式な配信を行いました。大量のバウンスを覚悟していましたが、結果は良好でした。バウンス率は約1%、配信停止もごくわずか、到達性の問題もありませんでした。本当に驚きました!
特に小細工はしませんでした。バッチ分割も、ゆっくりウォームアップも、凝った件名もなしです。一斉に送り、明らかに無効なアドレスは Plunk(正確には裏側の SES)がバウンス処理で自動的に除外してくれるのに任せました。唯一やったのは、最初の号の冒頭で短く率直な再自己紹介を入れたことです。たとえば「以前ブログを読んで登録してくれた方へ、長い沈黙ですみません」といった感じの一文です。これが配信停止を抑える上で一番効いたのだと思います。
コスト面では、全リストへの1回の配信がおよそ1ドルです。不定期配信のニュースレターなら、ほとんど負担になりません。

出典: Plunk
これが自分に合っている!
号ごとの原稿をフォルダの中の素朴な Markdown ファイルとして書き、バージョン管理しつつ、残りは小さな CLI に任せればいいと気づきました。これこそ自分が落ち着けるやり方です。自分と、温かいココア一杯と、エディタと、ターミナルと、git。それだけでいい。執筆の邪魔をするウェブダッシュボードはもう必要ありません。
全体はひとつのリポジトリに収まっています。
newsletter/
├── issues/ # one .md per edition (1.md, 2.md, ...)
├── send/ # the CLI I run locally
└── subscribe/ # tiny HTTP service behind the website signup formCLI の名前はsendです。できることは次のとおりです。
$ send help
Usage: send <COMMAND>
Commands:
new Create a new issue file and open $EDITOR
list List local issues
lint Check links in an issue (or all issues)
test Send a test email to myself
publish Publish the issue to all subscribed contacts
status Show contact-list and deliverability report
prune Delete unsubscribed contactssend publish 2を実行すると、プレビューと宛先数、そして実際に送信する前のy/N確認プロンプトが表示されます。件名は自動的にcorrode v0.N.0 # <topic>という形式で生成されます。セマンティックバージョニング風で、メジャーバージョンはずっと0のままです。なかなか 1.0 に到達しないプロジェクトにかけた小さなジョークです。
send statusでは、キャンペーンごとの到達性が表示されます。バウンス率は SES のしきい値に応じて色分けされ、日ごとのバウンス数や配信停止数も確認できるので、問題を早めに見つけられます。
send lintは、公開前に号の中のすべてのリンクをlycheeにかけてチェックします。私は lychee のメンテナでもあるので、ここでドッグフーディングするのは自然な選択でした。リンクチェックすらなかった昔の Tinyletter のウェブエディタと比べると、ちょっとした QOL の向上です。
サイト上の登録フォームは、私のサーバーで動く小さなsubscribeサービスに POST します。サービスはメールアドレスを検証し、ハニーポットフィールドが入力されているものは除外し、subscribe-requestedイベントとして Plunk に POST します。Plunk はコンタクトをunsubscribed状態で作成し、Action ワークフロー経由でトランザクショナルな確認メールを送信します。受信者がリンクをクリックして初めて、Plunk がステータスをsubscribedに切り替えます1。こちら側に戻ってくる webhook もコールバックも、ページ上の JavaScript も必要ありません。私は git に push するだけで、サーバーが変更を検知してサーバークレートをビルド・実行し、新しいバージョンがそのまま公開されます。稼働中のサービスは CPU もメモリもほとんど消費しません。
DNS の話を少しだけ
Plunk が私のドメインの代わりに送信するには、DNS に3つの設定が必要です。SPFレコード(SES がそのドメインからの送信を許可されていることを示すもの)、DKIM鍵(SES が送信メールに署名するためのもの)、そしてリターンパス用のMXレコード(バウンスを Plunk が読み取れる場所に戻すためのもの)です。いずれもサブドメイン配下に置きます。心配はいりません。Plunk が設定方法を正確に教えてくれるので、DNS プロバイダーのダッシュボードにレコードをコピー&ペーストするだけです。
ひとつだけ忘れてはいけないことがあります。Plunk のオプションである受信用 MX を、ドメインの apex(頂点)に追加しないことです。そうしてしまうと、現在受信箱を担当しているサービス(私の場合は mailbox.org)からメールが奪われ、返信が期待どおりに届かなくなります。
ちょっとしたハプニング
From:アドレスは、返信を受け取りたいなら実際に存在するメールボックスである必要があることを忘れていました。最初の号は存在しないメールボックスである[email protected]から送ってしまったのです。親切な読者の方(やあ Kevin!)が挨拶を返信してくれたのですが、そのメールはバウンスし、彼はバウンス通知を転送して知らせてくれました。そこで mailbox.org にエイリアスを作成して以来、返信はちゃんと受信箱に届いています。
リストはひとつに
ついでに、以前の endler.dev のニュースレターとcorrode.devのニュースレターもひとつのリストに統合しました。どちらもずっと私が書いていたものですし、2つを並行して運用する意味はあまりありませんでした。書いているのは同じ人間で、読者もほぼ重なっているのに、手間だけが2倍でした。
統合自体はあっさり終わりました。Tinyletter からエクスポートした CSV(元々の endler.dev リスト)と、fly.io のサービスから取った CSV(corrode.dev 公開時に集め始めたリスト)がありました。形式は同じです。両方を Plunk に入れましたが、重複排除も問題になりませんでした。最初の号では「すべての執筆活動をひとつのニュースレターにまとめます」と明確に伝えたので、読者が何に登録されているのか迷うこともありませんでした。2
今後はニュースレターはひとつだけです。もし合わないと感じたら、いつでも配信停止して、二度と私からのメールが届かないようにできます。気にしないでください。
伝えたいこと
もし自分でやってみようかと考えているなら、ぜひやってみてください。セルフホストは以前よりずっと簡単になっています。今はほぼすべてのパーツに優れたオープンソースのサービスがあります。一般的に、小さなものを自分で作ることは、それを本当に理解し、大事な部分を自分で持ち続けるための最良の方法のひとつです。この話だけでまた一本ブログが書けそうなので、興味があれば教えてください。
(やや場当たり的な)リポジトリをちょっと覗いてみたいという方は、メールをください。リンクをお送りします。たいして面白いものではありませんが、仕組みが気になるなら喜んで共有します。あるいは、もう少しきれいにしてちゃんとオープンソース化するまで待ってください。……それにはまた数年かかりそうです。
そして一番いいのは、下のフォームから登録して、私の仕組みを今すぐ試せることです!
記事をランダムに読む