0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KaiMailのWebhookでメールをプログラムで受信する

0
Posted at

はじめに

KaiMailは、独自ドメインのメールをHTTP Webhookとして受信できるメール転送サービスである。MXレコードを設定するだけで、そのドメイン宛のメールがJSON形式のPOSTリクエストとしてアプリケーションに届く。メールサーバーの運用は不要だ。

この記事では、KaiMailのWebhook連携でメールをプログラム処理する方法を、Flaskのコード例とともに説明する。

professional-high-resolution-photograph-20260309-101759-2.png

メールをプログラムで受信するのは面倒

開発者というのは、アイデアが浮かぶたびにドメインを取得してしまう生き物である。私の周りの開発者もそうだし、私自身もそうだ。ドメインにウェブサイトを載せるのは簡単で、今はホスティングサービスがいくらでもある。問題はメールの受信だ。

自前でPostfixやSendmailを立てるのは運用負荷が高い。そもそも自宅サーバーを誰でも立てることができた今は、ほとんどのプロバイダーは25番ポートを閉鎖してる。その上にSMTPの仕組みを理解している開発者は、Node.jsやReactを使える開発者より圧倒的に少なくなった。GmailやOutlookのおかげでメールの仕組みを意識する機会がなくなったからだろう。

Amazon SESやSendGrid Inbound Parseのようなクラウドサービスもあるが、設定が複雑で、メール受信だけのために使うにはオーバースペックなことが多い。

開発者が本当に求めているのは、こういうシンプルな体験だ。「このアドレスにメールが届いたら、その内容でHTTPエンドポイントを叩いてくれ。」

ユースケースは幅広い。サポートチケットの自動作成、請求書や領収書の処理、IoTデバイスからのアラート通知、社内の通知ルーティングなど、メール受信をトリガーにしたい処理は意外と多い。

KaiMailのWebhook連携の仕組み

全体のフローはシンプルである。

  1. 送信者がメールを送る
  2. KaiMailのMXサーバーがメールを受信する
  3. KaiMailが指定されたHTTPSエンドポイントにPOSTリクエストとしてJSONペイロードを配信する

必要なものは3つだ。

  • KaiMailのPLUSプラン以上のアカウント
  • カスタムドメイン(DNSのMXレコードをmail.kaimail.netに向ける)
  • Webhook配信先となるHTTPSエンドポイント

KaiMailの管理画面でメールボックスを作成し、配信方法を「Webhook」に設定してエンドポイントURLを入力するだけで準備は完了する。詳細なセットアップ手順は公式ドキュメントを参照してほしい。

ペイロードの構造

KaiMailがWebhookで送信するJSONペイロードは以下のような構造になっている。

{
  "version": "1.0",
  "timestamp": "2025-01-31T10:30:00Z",
  "headers": {
    "From": "alice@example.com",
    "To": "test@yourdomain.com",
    "Subject": "Quick question",
    "Date": "Fri, 31 Jan 2025 10:30:00 +0000",
    "Message-ID": "<abc123@example.com>"
  },
  "body_text": "Hi,\n\nDo you offer annual discounts?\n\nThanks,\nAlice",
  "body_html": null,
  "attachments": [],
  "envelope": {
    "sender": "alice@example.com",
    "recipient": "test@yourdomain.com"
  },
  "account": {
    "email": "you@example.com",
    "domain": "yourdomain.com",
    "mailbox": "test@yourdomain.com"
  },
  "metadata": {
    "authentication_results": {}
  }
}

主要なフィールドを見ていこう。

  • headers: 元のメールヘッダーがそのまま入る。Receivedのように複数値を持つヘッダーは配列として格納される。
  • body_text / body_html: プレーンテキストとHTML本文。テキストのみのメールではbody_htmlnullになり、HTMLのみならbody_textnullになる。マルチパートメールでは両方が存在する。
  • attachments: 添付ファイルの配列。filenamecontent_typesizeurl(プリサインURL、24時間で期限切れ)を持つ。本体はJSONに埋め込まれない。
  • envelope: SMTPエンベロープの送信者と受信者。From/Toヘッダーと異なる場合がある。
  • metadata: SPF、DKIM、ARCなどのメール認証結果。

Flaskで受信エンドポイントを作る

実際にWebhookを受信するFlaskアプリケーションを書いてみよう。まず最小限のバージョンから始める。

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/webhook", methods=["POST"])
def receive_webhook():
    payload = request.get_json()

    subject = payload.get("headers", {}).get("Subject", "(no subject)")
    sender = payload.get("headers", {}).get("From", "(unknown)")
    body = payload.get("body_text", "")

    print(f"From: {sender}")
    print(f"Subject: {subject}")
    print(f"Body: {body[:200]}")

    return jsonify({"status": "ok"}), 200

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

これだけでWebhookの受信は動くが、本番で使うなら署名検証が必須である。KaiMailはルートに設定されたWebhookシークレットを使い、HMAC-SHA256ですべてのペイロードに署名する。署名はX-KAI-Webhook-Signatureヘッダーにsha256=<hex_digest>の形式で送信される。

署名検証を追加した完全なバージョンはこうなる。

import hashlib
import hmac
import os

from flask import Flask, request, jsonify, abort

app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get("KAIMAIL_WEBHOOK_SECRET", "")


def verify_signature(payload_bytes, secret, received_signature):
    """KaiMailのWebhook署名を検証する"""
    expected = hmac.new(
        secret.encode("utf-8"),
        payload_bytes,
        hashlib.sha256,
    ).hexdigest()
    received_hex = received_signature.removeprefix("sha256=")
    return hmac.compare_digest(expected, received_hex)


@app.route("/webhook", methods=["POST"])
def receive_webhook():
    # 署名検証
    signature = request.headers.get("X-KAI-Webhook-Signature", "")
    if not verify_signature(request.get_data(), WEBHOOK_SECRET, signature):
        abort(403)

    payload = request.get_json()

    # トラッキングIDで重複チェック(実装は省略)
    tracking_id = request.headers.get("X-KAI-Tracking-ID", "")

    subject = payload.get("headers", {}).get("Subject", "(no subject)")
    sender = payload.get("headers", {}).get("From", "(unknown)")

    print(f"[{tracking_id}] From: {sender} / Subject: {subject}")

    # ここで実際の処理を行う(チケット作成、DB保存など)

    return jsonify({"status": "ok"}), 200


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

環境変数KAIMAIL_WEBHOOK_SECRETにKaiMail管理画面で表示される署名シークレットを設定して起動する。

export KAIMAIL_WEBHOOK_SECRET="your_signing_secret_here"
python app.py

署名検証のポイント

Webhookエンドポイントは公開URLなので、署名を検証しないと偽のメールデータを注入される可能性がある。実装上の注意点が2つある。

1つ目はsha256=プレフィックスの除去だ。ヘッダーの値はsha256=abc123...という形式だが、hmac.new().hexdigest()abc123...のみを返す。プレフィックスを除去せずに比較すると検証は常に失敗する。(実際、私たち自身のサンプルコードでまさにこのバグを見つけた。)

2つ目はhmac.compare_digestの使用だ。通常の==はタイミング攻撃に脆弱だが、hmac.compare_digestは定数時間で比較するためこの攻撃を防げる。

本番環境で気をつけること

Webhookを本番で運用する際に押さえておくべきポイントをまとめる。

即座に200を返し、処理は非同期で行う。 受信したらDBに保存して200を返し、実処理はバックグラウンドジョブで実行する。処理が遅いとタイムアウトでリトライが発生する。

X-KAI-Tracking-IDで重複を排除する。 配信ごとにユニークなIDが付与される。リトライで同じWebhookが複数回届く可能性があるので、このIDで処理済みかどうかをチェックする。

リトライの挙動を理解する。 5xx、408、429の場合、KaiMailは5秒、10秒、20秒の間隔で最大3回リトライする。その他の4xxはリトライされない。

HTTPSは必須である。 KaiMailはSSL証明書を検証する。開発中はngrokを使ったローカルテストが便利だ。

添付ファイルのURLは24時間で期限切れになる。 受信したらすぐにダウンロードして自前のストレージに保存すること。

まとめ

メールをプログラムで受信するのは、本来もっとシンプルであるべきだ。KaiMailのWebhook連携を使えば、メールサーバーの運用やSMTPの知識なしに、HTTPエンドポイントひとつでメール受信を実現できる。

この記事ではFlaskでの実装例を紹介したが、HTTP POSTを受け取れるフレームワークなら何でも対応できる。Express、FastAPI、Gin、何でもいい。

詳しい設定手順やペイロードの全仕様はWebhook連携ガイドを、ローカル環境でのテスト方法はPythonでKaiMailのWebhookをテストする方法を参照してほしい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?