Email & Notifications / Inbound Email
Getting Started
Tickets & Conversations
Automation & Workflows
Email & Notifications
入站邮件
直接从收到的邮件创建和回复工单。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']