Hosting My Own Newsletter

Matthias Endler

ニュースレターを自前で運用する

原文は Matthias Endler により に公開されました。 このブログを購読する

このブログでは何年も前からニュースレターをやっていましたが、長い間一通も配信していませんでした。これは、どうやってようやく再開にこぎつけたのか、その過程で何を学んだのかという話です。

最初にひとつ断っておきます。誤解を招いたことがあるので——「自前でホスティングする」と言っても、ニュースレターのプラットフォームを使っていないという意味です。登録用のバックエンドや配信に使っているCLIは自作で、記事本体はただのMarkdownファイルをgitリポジトリで管理しているだけです。配信のバックエンドとしては今でもPlunkを使っています(なのでSESやバウンス、サプレッションリスト、配信停止ページの面倒は見なくて済みます)。Plunkはオープンソースなので自前でホストすることもできますが、到達性まわりはエッジケースが多すぎるので、お金を払って誰かに運用してもらった方が気が楽です。🙃

Tinyletter時代

かつてのTinyletterのランディングページ。今は寂しい404になっている。
かつてのTinyletterのランディングページ。今は寂しい404。
出典: Wayback Machine

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

Tinyletterの執筆画面。シンプルなインターフェースがよくわかる。
Tinyletterの執筆画面。シンプルなインターフェースがよくわかる。

ただただ、うまく動いていました。そしてTinyletterはサービスを終了しました。

少し歴史を振り返ると、Tinyletterは2010年にPhilip Kaplanによって作られました。2010年10月31日、日曜日たった一日でコーディングされたと言われています。

その1年後にMailchimpに買収され、ファネルやセグメント、A/Bテストのことを考えずにパーソナルなニュースレターをやりたいライターたちにとって、静かにデファクトスタンダードな居場所になっていきました。

そして2023年後半、Mailchimp(現在はIntuit傘下)はサービス終了を発表しました。公式の表現では「ビジネス上の優先事項が変化した」ため、「マーケターを支援し、中小企業の成長を助けるツールの構築に注力する」とのことでした。ライターはそもそも彼らの主要顧客ではなかったのです。

Mailchimpによるサービス終了の告知。2023年後半。
Mailchimpのサービス終了告知(2023年後半)。
出典: EmailOctopus

Tinyletterが2024年2月29日にひっそりと幕を閉じる直前に、僕は購読者リストの最終バックアップを取りました。ただ、その後どうするかという計画はありませんでした。

否認

この時点で、僕はサードパーティのサービスを使うという考え自体に後ろ向きになっていました。同じことがまた繰り返されるかもしれないからです。

それでも一応あらゆる選択肢を検討してみましたが、どれも決め手に欠けました。

  • 高すぎる! ほとんどのサービスはコンタクト数で課金し、誰かに手紙を書くのではなくビジネスファネルを回すことを前提にしています。
  • マーケティング寄りすぎ! テンプレート、ドラッグ&ドロップのビルダー、A/Bテスト、エンゲージメントスコア、トラッキングピクセル。用語からして全部違うんです。僕はキャンペーンを回したいんじゃない、ただメールを送りたいだけなんです!
  • ハッカー向けじゃない。 MarkdownもCLIも、使っていて楽しいAPIもない。すべてはマーケティングチーム向けに作られたウェブダッシュボードの中で完結します。
  • オープンソースじゃない。 次のTinyletterが終了しても、また移行せずに続けられるようにしたいんです。
  • デフォルトでトラッキング! 開封トラッキング、クリックトラッキング、フッターごとに仕込まれたピクセル。誰が何を開いたかなんて知りたくありません。僕は書く、あなたは読む(あるいは読まない)。それで終わりです。

Fly.ioへの移行

ニュースレターはいつ再開するのかと何度も聞かれたので、fly.io上で間に合わせのものを作りました。小さなRust製APIと、購読者を入れたCSVファイル、ウェブサイト経由で登録できる仕組みです。配信は後で考えるとして、とりあえず登録だけでもできるようにしておこうという考えでした。

そしてリストはそのまま放置されました。

コールドリスト自体がそもそも問題だということが後になってわかりました。長い間連絡を取っていない人たちのリストに久しぶりにメールを送ると、メールプロバイダーに怪しまれてスパム判定されることがあるのです。そうなると、自分のニュースレターが自分に牙をむいてきます。

配信サービスを探して

これが断トツで一番大変な部分でした。ResendPostmarkSendGridMailgunAmazon SESなど、数多くのサービスを調べました。どれも、小さなニュースレターにはかなり高価だったり、APIがひどかったり、GDPRに準拠していなかったり、あるいは複雑すぎたりしました。

諦めかけたときにPlunkを見つけました。オープンソースで、料金はリストの規模に応じて変わり、APIもストレスなく使えます。自分では考えたくない到達性まわりの仕事(SES連携バウンス処理サプレッションリストホストされた配信停止ページ)をやってくれます。今では有料顧客として使っています。別に案件でも何でもなく、ただ純粋に気に入っているユーザーです。

小さなコントリビューションを送ってみたところ、10分でマージしてくれました。自分が本当にコミュニティの一員になれたような気がしました。

最初の本当の配信は、久しく連絡を取っていなかった1000件以上のコンタクトに向けて送られました。バウンスの波を覚悟していましたが、結果は上々でした。バウンス率は約1%、配信停止もごくわずか、到達性の問題もなし。驚きました!

特に凝ったことは何もしていません。バッチ分割も、じっくりウォームアップすることも、気の利いた件名もなし。一斉に送信して、あとはPlunk(正確にはその裏側のSES)がバウンス処理で明らかに無効なアドレスを自動的に除外してくれるのに任せました。唯一やったことといえば、最初の一通の冒頭で短く率直な再自己紹介を入れたこと——「やあ、昔僕のブログ記事を読んで登録してくれましたよね、長い間音沙汰なしでごめんなさい」といった感じのものです。これが配信停止を低く抑える上で一番効いたのだと思います。

コスト面では、全リストへの1回の配信でだいたい1ドルほどかかります。不定期に送るニュースレターなら、ほぼ無視できる金額です。

Plunkのダッシュボード。キャンペーンの概要と到達性レポートが表示されている。ご覧の通り、誰がメールを開いたかは追跡していない。
Plunkのダッシュボード。キャンペーンの概要と到達性レポートが表示されている。ご覧の通り、誰がメールを開いたかは追跡していない。
出典: 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 form

CLIの名前は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 contacts

send publish 2を実行すると、プレビューと受信者数が表示され、実際に送信する前にy/Nの確認プロンプトが出ます。件名は自動的にcorrode v0.N.0 # <topic>という形式で作られます。SemVer風で、メジャーバージョンはずっと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が読める場所に戻ってくるようにするもの)です。3つともサブドメイン配下に置かれます。心配はいりません。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

今後はニュースレターはひとつだけです。もし合わないと感じたら、いつでも配信停止して、二度と僕からのメールを受け取らないようにできます。気にしませんので。

あなたに伝えたいこと

もし自分でもやってみようかと考えているなら、ぜひやってみてください。セルフホスティングは以前よりずっと簡単になっています。今ではほぼすべてのパーツに対して優れたオープンソースのサービスがあります。一般的に、小さなものを自分で作ることは、それを本当に理解し、大切な部分を自分の手元に置き続けるための最良の方法のひとつです。その話だけでまた一本記事が書けそうなので、読みたい方は教えてください。

(やや場当たり的な)リポジトリをちょっと覗いてみたいという方は、メールをください。リンクをお送りします。たいして面白いものではありませんが、仕組みが気になるなら喜んで共有します。あるいは、もう少しきれいにしてちゃんとオープンソース化するまで待っていてください。それにはまた数年かかりそうです。

そして一番いいのは、下のフォームから登録して、僕の仕組みを今すぐ試せるところです!

  1. 確認のクリックはGDPRを遵守するために重要で、そのあたりはPlunkがやってくれます。

  2. バックアップについて:正本の購読者リストはPlunkが保持しており、いつでもCSVにエクスポートできます。APIもあるはずですが、まだ試していません。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント