KaiMailにHTTP APIによるメール送信機能が追加された

KaiMailのHTTP送信APIがリリースされた。カスタムドメインからHTTPS経由でメールを送信でき、SMTPライブラリは不要。サーバーレス環境やポート制限のあるネットワークからでも利用可能。認証、クォータ、DKIM署名はSMTP送信と同一。


ひとつざんげしよう。3月にSMTP送信をリリースしたとき、私はあれで話は終わりだと思っていた。ポート587、STARTTLS、DKIM署名、完了。地球上のすべてのメールクライアントはSMTPを話す。それ以上何が必要なのか?

ところが、かなり必要だったようだ。

リリース以来、同じようなフィードバックを何度も見てきた。「ホスティングプロバイダがポート587をブロックしている」「サーバーレス関数から呼んでるんだけどSMTPハンドシェイクがタイムアウトする」「JSONをPOSTして終わりにしたいだけなんだ」。そのたびに私は「わかってる。作業してるんだ」と答えていた。

その作業が完了した。KaiMailにHTTP送信APIができた。

Developer coding an email API on a laptop

これは一体何か

HTTP APIは、ポート587のSMTPで処理しているのと同じ送信パイプラインへの別の入り口だ。HTTP経由で送信しても、検証、DKIM署名、クォータ制御、配信ログはすべて同一のパイプラインを通る。変わるのは、話しかけ方だけである。

JSONをPOSTする。メッセージがキューに入り、tracking_idが返る。それだけだ。

これが重要なのは、SMTPが常に正しいツールとは限らないからだ。Netlify Functionsや制限の厳しいVPC内のAWS Lambdaなど、一部のプラットフォームは単純にアウトバウンドSMTPを許可していない。DigitalOceanのようなプラットフォームですらポートをブロックすることもあるし、ライブラリが重すぎたり、接続タイムアウトでまともに動かなかったりすることもある。HTTPはほぼ常に許可されている。関数がwebhookを呼べるなら、メールも送信できる。

仕組み

APIは https://kaimail.net/api/v1 にある。HTTP Basic認証を使い、認証情報はSMTPと同一だ:KaiMailアカウントのメールアドレスとSMTPパスワード。これは意図的な選択だ。すでにポート587で送信している人は、新しいキーやダッシュボードや覚えるべき新しいドキュメントが不要だ。

送信には2つのエンドポイントがある。

POST /api/v1/email/sendfromtosubject、そして text または html を渡す。サーバー側がMIMEメッセージを構築し、Date ヘッダと Message-ID も付与する。90%の場合はこれで十分だ。

POST /api/v1/email/send-raw — RFC 822 メッセージ全体をbase64エンコードしたものと、エンベロープ宛先を渡す。添付ファイル、カスタムヘッダ、Bcc、メッセージ構造の完全な制御が必要な場合に使う。

どちらも即座に 202 Accepted を返し、tracking_idを含む。メッセージはキューに登録されるだけで、まだ配信されていない。そのため、アプリケーションがSMTPの往復応答を待ってハングアップすることはない。

もっともシンプルな例を示そう:

curl -X POST https://kaimail.net/api/v1/email/send \
  -u "[email protected]:YOUR_SMTP_PASSWORD" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Support <[email protected]>",
    "to": ["[email protected]"],
    "subject": "ご登録ありがとうございます",
    "text": "こんにちは。ご登録ありがとうございます。"
  }'

Pythonでの同じ処理は以下の通りだ:

import requests

resp = requests.post(
    "https://kaimail.net/api/v1/email/send",
    auth=("[email protected]", "YOUR_SMTP_PASSWORD"),
    json={
        "from": "Support <[email protected]>",
        "to": ["[email protected]"],
        "subject": "ご登録ありがとうございます",
        "text": "こんにちは。ご登録ありがとうございます。",
    },
    timeout=30,
)
tracking_id = resp.json()["data"]["tracking_id"]

レスポンスの構造は、成功・エラーを問わず一貫している。すべてのレスポンスにサポート用の request_id が含まれ、エラーには人間向けのメッセージに加えて機械可読な code が含まれる。分岐はテキストではなくコードを使え。

送信後の状態確認

SMTPでは手軽に得られないのが、送信後の可視性だ。メッセージをサーバーに渡して祈るしかない。HTTP APIはこれを解決する。

GET /api/v1/email/<tracking_id> は、queued、sending、sent、failedの完全なステータスを返す。ワーカーが一時的なエラーに遭遇した場合、自動的に最大3回までリトライする。メッセージが失敗した場合、その理由がレスポンスに含まれる。

また、GET /api/v1/usage エンドポイントでクォータと残り送信数が確認できる。飛行前チェックや簡単なヘルスモニタに便利だ。

同じルールが適用される

送信者の要件はSMTPと完全に同一だ。送信できるのは所有しているアドレスからのみ:ドメインはアカウント上の有効なカスタムドメインである必要があり、完全なアドレスは登録済みのメールボックスでなければならない。メッセージは送信処理の中でドメインの鍵でDKIM署名される。

すでにSMTP送信の設定を済ませているなら、DNSレコードはすでに揃っている。追加の設定は不要だ。

役立つエラーハンドリング

エラー設計には予想以上の時間を費やした。APIは特定のコードを返すため、インテグレーション側で適切な対処ができる:

  • invalid_credentials (401) — パスワードが誤っている。ダッシュボードのログインパスワードではなく、SMTPパスワードを使え。
  • sending_not_allowed (403) — プランに送信機能が含まれていない。有料プランにアップグレードしろ。
  • domain_not_owned (403) — アカウントに登録されていないドメインから送信しようとしている。
  • quota_exceeded (429) — 月間上限に達した。/usage を確認してバックオフしろ。

完全なエラーリファレンスと制限値は HTTP Sending API ドキュメント に記載している。

SMTPとHTTP、どちらを使うか

SMTP を使うのは、デスクトップのメールクライアントから送信する場合、メーリングリストから送信する場合、あるいはすでにSMTP対応のアプリケーションがある場合だ。これは標準だ。ポート587が許可されていればどこでも動く。

HTTP を使うのは以下の場合だ:

  1. プラットフォームがアウトバウンドSMTPをブロックしている場合(サーバーレスや一部のマネージドホスティングでは一般的だ)。
  2. コードから送信していて、MIME生成よりJSONの方が楽な場合。
  3. SMTPレスポンスを解析せずに、プログラムから送信ステータスやクォータにアクセスしたい場合。
  4. 受信メールに webhook API を使っていて、送信も同じリクエスト/レスポンスモデルで扱いたい場合。

利用可能なプラン

HTTP送信は、SMTP送信を含むすべての有料プランで利用可能だ:Plus、Pro、Business。クォータプールを共有するため、別々の制限を追跡する必要はない。すでに有料プランを利用していれば、APIはすでに有効になっている。

Basicプランのユーザーはアップグレードして利用できる。アップグレード方法はSMTPと同じだ:有料プランを選び、SMTPパスワードを取得して送信を開始すればいい。

今後の展望

これは、私たちが自分たちでインテグレーションを構築していた頃に欲しかったAPIだ。ドキュメントは公開済み、エンドポイントは安定しており、私たち自身のサービスでも本番運用している。予期しない問題が発生した場合、すべてのレスポンスに含まれる request_id により、正確に何が起きたかを追跡できる。

試してみてほしい。cURLコマンドから始めて、アプリケーションに組み込んで、感想を聞かせてくれ。

完全なリファレンスは HTTP APIドキュメント にある。