mcpField
A
action-mailbox

action-mailbox

github.com/thinkoodle開発7188 回インストール8 週間前に更新
リポジトリ

action-mailbox について

Rails 開発者向けに、Action Mailbox による受信メールの振り分け、処理、拒否、テストを案内するスキルです。業務データの早期抽出、明示的なバウンス、実際のメール fixture によるテストを重視します。

主な機能

  1. 01宛先パターンによるメール振り分け
  2. 02役割を絞ったメールボックスクラス
  3. 03コールバックで検証とバウンス
  4. 04添付ファイルとマルチパートメールの処理
  5. 05.eml fixture によるテスト

活用シーン

  1. 01Rails アプリの受信メール設定
  2. 02サポートメールから業務レコードを作成
  3. 03無効な送信者へ説明付きで拒否
  4. 04メールの振り分けや処理の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/thinkoodle/rails-skills --skill action-mailbox

インストール後、Claude Code で/action-mailboxを入力して読み込まれたか確認します。

同梱ファイルreference.md

詳細

Rails Action Mailbox エキスパート

受信メールをコントローラーのようなメールボックスクラスにルーティングし、Rails アプリケーションで処理します。

基本方針

基本原則:

  1. メールボックスはメール用のコントローラーです — アドレスパターンでルーティングし、専用クラスで処理します
  2. 早めにデータを抽出し、後で焼却します — メールからドメインデータをすぐに永続化し、生のメールは一時的なものとして扱います
  3. 明示的にバウンスします — 不正なメールは内容が伝わるバウンスメッセージで拒否し、黙って無視しないでください
  4. 実際のメールフィクスチャでテストします — 手作業で作成した Mail オブジェクトではなく、.eml ファイルを使用します
  5. 関心事ごとにメールボックスを1つ作成します — メールボックスの役割を絞り、process 内で分岐させるのではなく、ルーティングを分けます

このスキルを使う場面

  • Rails アプリで Action Mailbox をセットアップする場合
  • 受信プロバイダー(Mailgun、SendGrid、Postmark、Postfix など)を設定する場合
  • 受信メールを処理するメールボックスクラスを作成する場合
  • メールを適切なメールボックスにルーティングする場合
  • Active Storage でメールの添付ファイルを処理する場合
  • 無効なメールをバウンスまたは拒否する場合
  • 受信メール処理をテストする場合
  • メールの配信や処理に関する問題をデバッグする場合

手順

ステップ 1:Action Mailbox をインストールする
bin/rails action_mailbox:install
bin/rails db:migrate

以下が作成されます。

  • app/mailboxes/application_mailbox.rb — ルーティングのハブ
  • action_mailbox_inbound_emails テーブル用のマイグレーション
  • Active Storage のテーブル(まだ存在しない場合)

セットアップを確認します。

# Check the migration ran
bin/rails runner "puts ActionMailbox::InboundEmail.table_exists?"

# Check conductor is available (dev only)
# Visit http://localhost:3000/rails/conductor/action_mailbox/inbound_emails
ステップ 2:ルーティングを設定する

ApplicationMailbox がルーターです。メールの宛先をパターンと照合し、対応するメールボックスクラスに振り分けます。

# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
  # Route by To/CC/BCC address patterns (regex or string)
  routing(/^support@/i      => :support)
  routing(/^reply\+(.+)@/i  => :replies)
  routing(/@invoices\./i    => :invoices)
  routing(all:              => :catch_all)  # Fallback — use sparingly
end

ルーティングのルール:

  • パターンは to、cc、bcc フィールドと照合されます
  • 最初に一致したルールが適用されます。順序が重要です
  • すべてのメールを処理する必要がある場合に限り、全件捕捉用として all: を使用します
  • 一致しないメールは pending のままになり、焼却されます

⚠️ よくある間違い: ルーティングが to だけでなく、すべての宛先フィールドと照合されることを忘れてしまうことです。一致するアドレスが CC に含まれているメールもルーティングされます。

ステップ 3:メールボックスクラスを生成する
bin/rails generate mailbox support

app/mailboxes/support_mailbox.rb が作成されます。

class SupportMailbox < ApplicationMailbox
  def process
    # Your processing logic here
  end
end
ステップ 4:process メソッドを実装する

process メソッドでは、メールからデータを抽出し、ドメインモデルに永続化します。

class SupportMailbox < ApplicationMailbox
  def process
    ticket = SupportTicket.create!(
      subject: mail.subject,
      body: mail.decoded,
      from_email: mail.from.first,
      from_name: mail.from_address&.display_name
    )

    # Handle attachments
    mail.attachments.each do |attachment|
      ticket.files.attach(
        io: StringIO.new(attachment.decoded),
        filename: attachment.filename,
        content_type: attachment.content_type
      )
    end
  end
end

mail(Mail::Message)で利用できる主なアクセサー:

  • mail.to — To アドレスの配列
  • mail.from — From アドレスの配列
  • mail.cc — CC アドレスの配列
  • mail.subject — 件名
  • mail.body.decoded / mail.decoded — デコード済みの本文テキスト
  • mail.date — 送信日時
  • mail.message_id — 一意のメッセージ ID
  • mail.in_reply_to — このメールが返信しているメッセージ ID
  • mail.attachments — 添付ファイルの配列
  • mail.multipart? — メールが複数のパートで構成されているかどうか
  • mail.text_part / mail.html_part — 特定の MIME パート

inbound_email アクセサー:

  • inbound_email — ActionMailbox::InboundEmail の Active Record オブジェクト
  • inbound_email.message_id — RFC 2822 の Message-ID
  • inbound_email.source — 生のメールソース(RFC 822)
ステップ 5:ガードや副作用にコールバックを使う

Action Mailbox には、before_processing、after_processing、around_processing の各コールバックがあります。

class SupportMailbox < ApplicationMailbox
  before_processing :ensure_known_sender
  before_processing :reject_spam
  after_processing :notify_team

  def process
    create_ticket_from_email
  end

  private

  def ensure_known_sender
    unless User.exists?(email_address: mail.from.first)
      bounce_with SupportMailer.unknown_sender(inbound_email)
    end
  end

  def reject_spam
    if SpamDetector.spam?(mail)
      bounce_with SupportMailer.spam_rejected(inbound_email)
    end
  end

  def notify_team
    SupportNotificationJob.perform_later(@ticket)
  end
end

⚠️ よくある間違い: bounce_with が処理を中断することを理解していないことです。ステータスを bounced に設定し、バウンスメールを送信して処理を停止します。そのため、process メソッドは実行されません。

ステップ 6:正しくバウンスする

bounce_with は Action Mailer のメッセージを受け取り、次の処理を行います。

  1. バウンスメールを送信します
  2. inbound_email.status を bounced に設定します
  3. 以降の処理を中断します
# In a before_processing callback:
bounce_with UserMailer.not_authorized(inbound_email)

# The mailer is a normal Action Mailer:
class UserMailer < ApplicationMailer
  def not_authorized(inbound_email)
    @email = inbound_email
    mail(
      to: @email.mail.from.first,
      subject: "Unable to process your email"
    )
  end
end

バウンスのルール:

  • process 内ではなく、必ず before_processing コールバック内でバウンスします
  • 必ず意味のあるバウンスメールを送信します。何も通知せずに破棄すると、送信者を困惑させます
  • スパムはバウンスせず、配信済みとしてマークするか、何も通知せずに焼却します
ステップ 7:イングレスプロバイダーを設定する

config/environments/production.rb でイングレスを設定します:

config.action_mailbox.ingress = :mailgun  # or :sendgrid, :postmark, :mandrill, :relay

認証情報は暗号化された認証情報に保存します:

bin/rails credentials:edit
action_mailbox:
  ingress_password: "strong-random-password"     # For relay/postmark/sendgrid
  mailgun_signing_key: "your-mailgun-key"        # For Mailgun
  mandrill_api_key: "your-mandrill-key"          # For Mandrill

または環境変数を使用します:

  • RAILS_INBOUND_EMAIL_PASSWORD — リレーイングレスのパスワード
  • MAILGUN_INGRESS_SIGNING_KEY — Mailgun の署名キー
  • MANDRILL_INGRESS_API_KEY — Mandrill の API キー

プロバイダーごとのエンドポイント URL と設定の詳細については、reference.md を参照してください。

ステップ 8:メールボックスをテストする

ActionMailbox::TestCase とそのヘルパーを使用します:

require "test_helper"

class SupportMailboxTest < ActionMailbox::TestCase
  test "creates a support ticket from inbound email" do
    assert_difference "SupportTicket.count", 1 do
      receive_inbound_email_from_mail(
        to: "[email protected]",
        from: "[email protected]",
        subject: "Help needed",
        body: "I can't log in"
      )
    end

    ticket = SupportTicket.last
    assert_equal "Help needed", ticket.subject
    assert_equal "[email protected]", ticket.from_email
  end

  test "bounces email from unknown sender" do
    inbound_email = receive_inbound_email_from_mail(
      to: "[email protected]",
      from: "[email protected]",
      subject: "Hello"
    )

    assert inbound_email.bounced?
  end

  test "routes to support mailbox" do
    assert_equal SupportMailbox, ApplicationMailbox.route(
      receive_inbound_email_from_mail(to: "[email protected]")
    )
  end
end

.eml フィクスチャファイルを使ったテスト:

# Save real emails as test/fixtures/files/welcome_email.eml
test "processes a real email fixture" do
  receive_inbound_email_from_source(
    file_fixture("welcome_email.eml").read
  )
end

⚠️ よくある間違い: 現実的なメールフィクスチャではなく、手作業で作成した文字列を使ってテストすることです。実際のメールには、単純な文字列では再現できないヘッダー、MIME 境界、エンコーディングがあります。実際のメールを .eml ファイルとして保存してください。

ステップ 9:ローカルテストに Conductor を使用する

開発環境で、次のページにアクセスします:

http://localhost:3000/rails/conductor/action_mailbox/inbound_emails

Conductor では、次の操作ができます:

  • 新しい受信メールを手動で作成する
  • すべての受信メールとそのステータスを表示する
  • メールを再配信して再処理する
  • 実際のイングレスを設定せずにルーティングをテストする

curl からも利用できます:

curl -X POST http://localhost:3000/rails/conductor/action_mailbox/inbound_emails \
  -F "inbound_email[raw_email_file]=@path/to/email.eml"
ステップ 10:焼却を設定する

処理済みのメールは、デフォルトで 30 日後に焼却(削除)されます。

# config/environments/production.rb
config.action_mailbox.incinerate_after = 14.days  # Override default

重要: 元のメールに依存する前に、必要なデータをすべて process 内で抽出してください。焼却後は、元のメールとその Active Storage blob は削除されます。

焼却の対象となるステータス: delivered、failed、bounced

クイックリファレンス

InboundEmail のライフサイクル
  pending → processing → delivered
                       → failed (exception raised)
                       → bounced (bounce_with called)
メールアドレスのマッチングパターン
# Exact local part
routing("[email protected]" => :support)

# Regex on local part
routing(/^reply\+(.+)@/i => :replies)

# Domain matching
routing(/@billing\./i => :billing)

# Catch-all (last resort)
routing(all: => :catch_all)
Mailbox クラスのテンプレート
class ExampleMailbox < ApplicationMailbox
  before_processing :validate_sender

  def process
    # 1. Find or create related records
    user = User.find_by!(email_address: mail.from.first)

    # 2. Extract and persist data
    record = user.messages.create!(
      subject: mail.subject,
      body: mail.decoded,
      received_at: mail.date
    )

    # 3. Handle attachments
    mail.attachments.each do |attachment|
      record.files.attach(
        io: StringIO.new(attachment.decoded),
        filename: attachment.filename,
        content_type: attachment.content_type
      )
    end

    # 4. Trigger side effects
    NotificationJob.perform_later(record)
  end

  private

  def validate_sender
    unless User.exists?(email_address: mail.from.first)
      bounce_with ExampleMailer.unknown_sender(inbound_email)
    end
  end
end
テストヘルパー
# From ActionMailbox::TestCase / ActionMailbox::TestHelper

# Create and process an inbound email from params
receive_inbound_email_from_mail(to:, from:, subject:, body:, **headers)

# Create and process from raw RFC 822 source
receive_inbound_email_from_source(source)

# Create without processing (for routing tests)
create_inbound_email_from_mail(to:, from:, subject:, body:)
create_inbound_email_from_source(source)

# Status checks on returned InboundEmail
inbound_email.delivered?
inbound_email.bounced?
inbound_email.failed?
inbound_email.pending?
inbound_email.processing?

避けるべきアンチパターン

  1. 巨大な process メソッド — サービスオブジェクトに切り出します。メールボックスは実装ではなく、処理を統括する役割を担うべきです
  2. バウンス処理がない — 処理前に、必ず送信者とコンテンツを検証します
  3. マルチパートメールを無視する — mail.body だけでなく、mail.text_part または mail.html_part を使用します
  4. 焼却後も元のメールに依存する — すぐにデータをドメインモデルに抽出します
  5. フィルタリングなしのすべてを受け取るルーティング — routing(all:) はスパムも受け取るため、厳格にフィルタリングします
  6. 正常系だけをテストする — バウンス、不明な送信者、必須フィールドの欠落、スパムもテストします
  7. イングレスのパスワードをハードコードする — 認証情報または環境変数を使用します
  8. コールバック内で処理する — コールバックはガードのみに使用し、ビジネスロジックは process に記述します
  9. Active Job を設定しない — Action Mailbox は Active Job 経由で非同期にルーティングするため、キューアダプターが動作することを確認します
  10. Active Storage を忘れる — Action Mailbox は Active Storage 経由で元のメールを保存するため、両方をマイグレーションする必要があります

コメント