action-mailbox について
Rails 開発者向けに、Action Mailbox による受信メールの振り分け、処理、拒否、テストを案内するスキルです。業務データの早期抽出、明示的なバウンス、実際のメール fixture によるテストを重視します。
主な機能
- 01宛先パターンによるメール振り分け
- 02役割を絞ったメールボックスクラス
- 03コールバックで検証とバウンス
- 04添付ファイルとマルチパートメールの処理
- 05.eml fixture によるテスト
活用シーン
- 01Rails アプリの受信メール設定
- 02サポートメールから業務レコードを作成
- 03無効な送信者へ説明付きで拒否
- 04メールの振り分けや処理の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thinkoodle/ rails-skills --skill action-mailboxインストール後、Claude Code で/action-mailboxを入力して読み込まれたか確認します。
同梱ファイルreference.md
詳細
Rails Action Mailbox エキスパート
受信メールをコントローラーのようなメールボックスクラスにルーティングし、Rails アプリケーションで処理します。
基本方針
基本原則:
- メールボックスはメール用のコントローラーです — アドレスパターンでルーティングし、専用クラスで処理します
- 早めにデータを抽出し、後で焼却します — メールからドメインデータをすぐに永続化し、生のメールは一時的なものとして扱います
- 明示的にバウンスします — 不正なメールは内容が伝わるバウンスメッセージで拒否し、黙って無視しないでください
- 実際のメールフィクスチャでテストします — 手作業で作成した Mail オブジェクトではなく、
.emlファイルを使用します - 関心事ごとにメールボックスを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— 一意のメッセージ IDmail.in_reply_to— このメールが返信しているメッセージ IDmail.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-IDinbound_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 のメッセージを受け取り、次の処理を行います。
- バウンスメールを送信します
inbound_email.statusをbouncedに設定します- 以降の処理を中断します
# 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?
避けるべきアンチパターン
- 巨大な process メソッド — サービスオブジェクトに切り出します。メールボックスは実装ではなく、処理を統括する役割を担うべきです
- バウンス処理がない — 処理前に、必ず送信者とコンテンツを検証します
- マルチパートメールを無視する —
mail.bodyだけでなく、mail.text_partまたはmail.html_partを使用します - 焼却後も元のメールに依存する — すぐにデータをドメインモデルに抽出します
- フィルタリングなしのすべてを受け取るルーティング —
routing(all:)はスパムも受け取るため、厳格にフィルタリングします - 正常系だけをテストする — バウンス、不明な送信者、必須フィールドの欠落、スパムもテストします
- イングレスのパスワードをハードコードする — 認証情報または環境変数を使用します
- コールバック内で処理する — コールバックはガードのみに使用し、ビジネスロジックは
processに記述します - Active Job を設定しない — Action Mailbox は Active Job 経由で非同期にルーティングするため、キューアダプターが動作することを確認します
- Active Storage を忘れる — Action Mailbox は Active Storage 経由で元のメールを保存するため、両方をマイグレーションする必要があります