メールと通知 / 受信メール
受信メール
受信メールからチケットの作成や返信を直接行えます。EscalatedはMailgun、Postmark、AWS SESのWebhookと、フォールバックとしてのIMAPポーリングをサポートしています。
仕組み
- メールプロバイダーがサポートアドレス(例:
support@yourapp.com)でメッセージを受信 - プロバイダーがWebhook経由でアプリに転送(またはIMAPポーリングで取得)
- Escalatedがペイロードを正規化し、件名のリファレンス(例:
[ESC-00001])またはIn-Reply-Toヘッダーでスレッドマッチを確認 - 一致したメールは返信を追加、一致しないメールは新しいチケットを作成(不明な送信者にはゲストチケット)
設定
管理設定で受信メールを有効にし、アダプターを設定するか、環境変数で設定します。管理設定は環境変数/設定ファイルの値よりも優先されます。
WebhookのURL
メールプロバイダーの受信Webhookを以下のURLに向けてください。これらのルートは認証不要です(署名検証を使用します)。
| プロバイダー | Webhook URL |
|---|---|
| Mailgun | POST /support/inbound/mailgun |
| Postmark | POST /support/inbound/postmark |
| AWS SES | POST /support/inbound/ses |
IMAPポーリング
Webhookをサポートしないメールプロバイダーの場合は、IMAPポーリングコマンドをスケジュールで使用してください。
機能
- 件名リファレンスとIn-Reply-To / Referencesヘッダーによるスレッド検出
- 不明な送信者向けのゲストチケット(表示名の自動生成付き)
- メール返信到着時の解決済みまたはクローズ済みチケットの自動再オープン
- Message-IDヘッダーによる重複検出(重複処理の防止)
- サイズとファイル数の制限が設定可能な添付ファイル処理
- 監査ログ -- すべての受信メールがデバッグとコンプライアンスのために記録
- 管理者設定可能 -- すべての設定を管理パネルから管理(環境変数/設定ファイルのフォールバック付き)
設定
# .env
ESCALATED_INBOUND_EMAIL=true
ESCALATED_INBOUND_ADDRESS=support@yourapp.com
# Mailgun
ESCALATED_INBOUND_ADAPTER=mailgun
ESCALATED_MAILGUN_SIGNING_KEY=your-signing-key
# Postmark
ESCALATED_INBOUND_ADAPTER=postmark
ESCALATED_POSTMARK_INBOUND_TOKEN=your-token
# AWS SES
ESCALATED_INBOUND_ADAPTER=ses
ESCALATED_SES_TOPIC_ARN=arn:aws:sns:us-east-1:...
# IMAP
ESCALATED_INBOUND_ADAPTER=imap
ESCALATED_IMAP_HOST=imap.gmail.com
ESCALATED_IMAP_USERNAME=support@yourapp.com
ESCALATED_IMAP_PASSWORD=your-app-password
IMAPポーリング
// routes/console.php
Schedule::command('escalated:poll-imap')->everyMinute();
設定
# config/initializers/escalated.rb
Escalated.configure do |config|
config.inbound_email_enabled = true
config.inbound_email_adapter = :mailgun
config.inbound_email_address = "support@yourapp.com"
config.mailgun_signing_key = ENV["ESCALATED_MAILGUN_SIGNING_KEY"]
end
IMAPポーリング
# config/recurring.yml (Solid Queue)
poll_imap:
class: Escalated::PollImapJob
schedule: every minute
設定
# settings.py
ESCALATED = {
"INBOUND_EMAIL_ENABLED": True,
"INBOUND_EMAIL_ADAPTER": "mailgun",
"INBOUND_EMAIL_ADDRESS": "support@yourapp.com",
"MAILGUN_SIGNING_KEY": os.environ.get("ESCALATED_MAILGUN_SIGNING_KEY"),
}
IMAPポーリング
# crontab or Celery beat
python manage.py poll_imap
設定
// config/escalated.ts
inboundEmail: {
enabled: true,
adapter: 'mailgun',
address: 'support@yourapp.com',
mailgunSigningKey: Env.get('ESCALATED_MAILGUN_SIGNING_KEY'),
}
IMAPポーリング
# Cron or @adonisjs/scheduler
node ace escalated:poll-imap
Filamentはescalated-laravelと同じ受信メール設定を使用します。Laravelタブを参照してください。
IMAPポーリング
同じLaravelスケジューラーコマンドを使用します。Laravelタブを参照してください。
設定
wp-adminのEscalated -> Settings -> Emailで受信メールを設定します。Mailgun、Postmark、AWS SESのWebhookをサポートしています。
IMAPポーリング
IMAPポーリングはWP-Cron経由で実行されます。IMAPが受信アダプターとして設定されると、プラグインが自動的にスケジュールします。
Inbound email is configured entirely on the server side. No mobile-specific setup is needed.
Tickets created via email appear in the Flutter app exactly like any other ticket. Thread detection, guest tickets, and attachment handling are all managed by your backend — the mobile app simply displays the results through the API.
Note: See your backend framework's tab for inbound email configuration (adapters, webhook URLs, IMAP polling).
Inbound email is configured entirely on the server side. No mobile-specific setup is needed.
Tickets created via email appear in the React Native app exactly like any other ticket. Thread detection, guest tickets, and attachment handling are all managed by your backend — the mobile app simply displays the results through the API.
Note: See your backend framework's tab for inbound email configuration (adapters, webhook URLs, IMAP polling).
Webhook endpoint
The .NET bundle exposes a single webhook for all providers. Configure Postmark and/or Mailgun to POST inbound mail to:
POST /support/webhook/email/inbound?adapter=postmark
POST /support/webhook/email/inbound?adapter=mailgun
You can also pass the adapter via the X-Escalated-Adapter header instead of a query parameter.
Configuration
Set the shared inbound secret and the mail domain (used for signed Reply-To and canonical Message-ID headers) in appsettings.json or via environment variables:
{
"Escalated": {
"Mail": {
"Domain": "support.yourapp.com",
"InboundSecret": "a-long-random-value"
}
}
}
# .env
Escalated__Mail__Domain=support.yourapp.com
Escalated__Mail__InboundSecret=a-long-random-value
The InboundSecret is symmetric — it's used to sign outbound Reply-To addresses and to verify inbound webhook requests, so forged emails that target a stolen reply address are rejected via timing-safe HMAC comparison.
Provider setup
Each provider signs its webhook and expects you to forward that signature via the X-Escalated-Inbound-Secret header.
Postmark — in your server settings under Inbound → Webhook URL:
https://yourapp.com/support/webhook/email/inbound?adapter=postmark
Add a custom header X-Escalated-Inbound-Secret: <your secret>.
Mailgun — under Receiving → Routes, create a "Forward" action pointing at:
https://yourapp.com/support/webhook/email/inbound?adapter=mailgun
Set the HMAC header the same way.
Testing
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Escalated-Inbound-Secret: <your secret>" \
-d '{
"FromFull": {"Email": "customer@example.com", "Name": "Customer"},
"To": "support@example.com",
"Subject": "Hello",
"TextBody": "Help please",
"MessageID": "<abc@mail>"
}' \
"https://yourapp.com/support/webhook/email/inbound?adapter=postmark"
The response shape:
{
"inboundId": 42,
"status": "created",
"outcome": "created_new",
"ticketId": 7,
"replyId": null,
"pendingAttachmentDownloads": []
}
Provider-hosted attachments (Mailgun's larger files, for example) appear in pendingAttachmentDownloads so a background worker can fetch and persist them out-of-band.
Webhook endpoint
The Phoenix library exposes a single webhook controller for all providers. Configure Postmark and/or Mailgun to POST inbound mail to:
POST /support/webhook/email/inbound?adapter=postmark
POST /support/webhook/email/inbound?adapter=mailgun
You can also pass the adapter via the x-escalated-adapter header instead of a query parameter.
Configuration
Set the shared inbound secret and mail domain (used for signed Reply-To and canonical Message-ID headers) in config/runtime.exs:
config :escalated,
mail_domain: System.get_env("ESCALATED_MAIL_DOMAIN", "support.yourapp.com"),
email_inbound_secret: System.fetch_env!("ESCALATED_INBOUND_SECRET"),
inbound_parsers: [
Escalated.Services.Email.Inbound.PostmarkParser,
Escalated.Services.Email.Inbound.MailgunParser
]
The email_inbound_secret is symmetric — it signs outbound Reply-To addresses and verifies inbound webhook requests, so forged emails that target a stolen reply address are rejected via timing-safe comparison (Plug.Crypto.secure_compare/2).
Wiring
Register the route in your Phoenix router:
scope "/support/webhook/email", Escalated.Controllers do
pipe_through :api
post "/inbound", InboundEmailController, :inbound
end
Provider setup
Each provider signs its webhook and expects you to forward that signature via the x-escalated-inbound-secret header.
Postmark — in your server settings under Inbound → Webhook URL:
https://yourapp.com/support/webhook/email/inbound?adapter=postmark
Add a custom header x-escalated-inbound-secret: <your secret>.
Mailgun — under Receiving → Routes, create a "Forward" action pointing at:
https://yourapp.com/support/webhook/email/inbound?adapter=mailgun
Set the HMAC header the same way.
Testing
curl -X POST \
-H "Content-Type: application/json" \
-H "x-escalated-inbound-secret: <your secret>" \
-d '{
"FromFull": {"Email": "customer@example.com", "Name": "Customer"},
"To": "support@example.com",
"Subject": "Hello",
"TextBody": "Help please",
"MessageID": "<abc@mail>"
}' \
"https://yourapp.com/support/webhook/email/inbound?adapter=postmark"
The response shape:
{
"status": "created",
"outcome": "created_new",
"ticket_id": 7,
"reply_id": null,
"pending_attachment_downloads": []
}
Provider-hosted attachments (Mailgun's larger files, for example) appear in pending_attachment_downloads so a background worker can fetch and persist them out-of-band.
Webhook endpoint
The Go module exposes a single webhook handler for all providers. Configure Postmark and/or Mailgun to POST inbound mail to:
POST /escalated/webhook/email/inbound?adapter=postmark
POST /escalated/webhook/email/inbound?adapter=mailgun
You can also pass the adapter via the X-Escalated-Adapter header instead of a query parameter.
Configuration
Set the shared inbound secret and mail domain (used for signed Reply-To and canonical Message-ID headers) via environment variables or directly on email.Config:
ESCALATED_MAIL_DOMAIN=support.yourapp.com
ESCALATED_INBOUND_SECRET=a-long-random-value
cfg := email.Config{
MailDomain: os.Getenv("ESCALATED_MAIL_DOMAIN"),
InboundSecret: os.Getenv("ESCALATED_INBOUND_SECRET"),
}
The InboundSecret is symmetric — it signs outbound Reply-To addresses and verifies inbound webhook requests, so forged emails that target a stolen reply address are rejected via timing-safe HMAC comparison (hmac.Equal).
Wiring
Register the handler on your http.ServeMux:
svc := email.NewInboundEmailService(router, writer)
handler := handlers.NewInboundEmailHandler(svc, parsers, cfg.InboundSecret)
mux.Handle("POST /escalated/webhook/email/inbound", handler)
Provider setup
Each provider signs its webhook and expects you to forward that signature via the X-Escalated-Inbound-Secret header.
Postmark — in your server settings under Inbound → Webhook URL:
https://yourapp.com/escalated/webhook/email/inbound?adapter=postmark
Add a custom header X-Escalated-Inbound-Secret: <your secret>.
Mailgun — under Receiving → Routes, create a "Forward" action pointing at:
https://yourapp.com/escalated/webhook/email/inbound?adapter=mailgun
Set the HMAC header the same way.
Testing
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Escalated-Inbound-Secret: <your secret>" \
-d '{
"FromFull": {"Email": "customer@example.com", "Name": "Customer"},
"To": "support@example.com",
"Subject": "Hello",
"TextBody": "Help please",
"MessageID": "<abc@mail>"
}' \
"https://yourapp.com/escalated/webhook/email/inbound?adapter=postmark"
The response shape:
{
"status": "created",
"outcome": "created_new",
"ticket_id": 7,
"reply_id": null,
"pending_attachment_downloads": []
}
Provider-hosted attachments (Mailgun's larger files, for example) appear in pending_attachment_downloads so a background worker can fetch and persist them out-of-band.
Webhook endpoint
The Spring Boot starter exposes a single webhook for all providers. Configure Postmark and/or Mailgun to POST inbound mail to:
POST /escalated/webhook/email/inbound?adapter=postmark
POST /escalated/webhook/email/inbound?adapter=mailgun
You can also pass the adapter via the X-Escalated-Adapter header instead of a query parameter.
Configuration
Set the shared inbound secret and mail domain (used for signed Reply-To and canonical Message-ID headers) in application.yml:
escalated:
mail:
domain: support.yourapp.com
inbound-secret: ${ESCALATED_INBOUND_SECRET}
Or via environment variables:
ESCALATED_MAIL_DOMAIN=support.yourapp.com
ESCALATED_INBOUND_SECRET=a-long-random-value
The inbound-secret is symmetric — it signs outbound Reply-To addresses and verifies inbound webhook requests, so forged emails that target a stolen reply address are rejected via timing-safe HMAC comparison (MessageDigest.isEqual).
Provider setup
Each provider signs its webhook and expects you to forward that signature via the X-Escalated-Inbound-Secret header.
Postmark — in your server settings under Inbound → Webhook URL:
https://yourapp.com/escalated/webhook/email/inbound?adapter=postmark
Add a custom header X-Escalated-Inbound-Secret: <your secret>.
Mailgun — under Receiving → Routes, create a "Forward" action pointing at:
https://yourapp.com/escalated/webhook/email/inbound?adapter=mailgun
Set the HMAC header the same way.
Testing
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Escalated-Inbound-Secret: <your secret>" \
-d '{
"FromFull": {"Email": "customer@example.com", "Name": "Customer"},
"To": "support@example.com",
"Subject": "Hello",
"TextBody": "Help please",
"MessageID": "<abc@mail>"
}' \
"https://yourapp.com/escalated/webhook/email/inbound?adapter=postmark"
The response shape:
{
"status": "created",
"outcome": "CREATED_NEW",
"ticketId": 7,
"replyId": null,
"pendingAttachmentDownloads": []
}
Provider-hosted attachments (Mailgun's larger files, for example) appear in pendingAttachmentDownloads so a background worker can fetch and persist them out-of-band.
Webhook endpoint
The Symfony bundle exposes a single webhook controller for all providers. Configure Postmark and/or Mailgun to POST inbound mail to:
POST /escalated/webhook/email/inbound?adapter=postmark
POST /escalated/webhook/email/inbound?adapter=mailgun
You can also pass the adapter via the X-Escalated-Adapter header instead of a query parameter.
Configuration
Set the shared inbound secret and mail domain (used for signed Reply-To and canonical Message-ID headers) under the escalated: bundle config:
# config/packages/escalated.yaml
escalated:
mail_domain: '%env(ESCALATED_MAIL_DOMAIN)%'
inbound_secret: '%env(ESCALATED_INBOUND_SECRET)%'
# .env
ESCALATED_MAIL_DOMAIN=support.yourapp.com
ESCALATED_INBOUND_SECRET=a-long-random-value
The inbound_secret is symmetric — it signs outbound Reply-To addresses and verifies inbound webhook requests, so forged emails that target a stolen reply address are rejected via timing-safe comparison (hash_equals).
Provider setup
Each provider signs its webhook and expects you to forward that signature via the X-Escalated-Inbound-Secret header.
Postmark — in your server settings under Inbound → Webhook URL:
https://yourapp.com/escalated/webhook/email/inbound?adapter=postmark
Add a custom header X-Escalated-Inbound-Secret: <your secret>.
Mailgun — under Receiving → Routes, create a "Forward" action pointing at:
https://yourapp.com/escalated/webhook/email/inbound?adapter=mailgun
Set the HMAC header the same way.
Testing
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Escalated-Inbound-Secret: <your secret>" \
-d '{
"FromFull": {"Email": "customer@example.com", "Name": "Customer"},
"To": "support@example.com",
"Subject": "Hello",
"TextBody": "Help please",
"MessageID": "<abc@mail>"
}' \
"https://yourapp.com/escalated/webhook/email/inbound?adapter=postmark"
The response shape:
{
"status": "created",
"outcome": "created_new",
"ticket_id": 7,
"reply_id": null,
"pending_attachment_downloads": []
}
Provider-hosted attachments (Mailgun's larger files, for example) appear in pending_attachment_downloads so a background worker can fetch and persist them out-of-band.
Adding a custom parser
The bundle discovers inbound parsers by the escalated.inbound_parser tag. To add a new one, implement Escalated\Symfony\Mail\Inbound\InboundEmailParser and autoconfigure:
# config/services.yaml
services:
_instanceof:
Escalated\Symfony\Mail\Inbound\InboundEmailParser:
tags: ['escalated.inbound_parser']