Repository navigation
Mail In to a Webhook
Written on 26 September 2026 from
hmailserver/docs/InboundWebhooks.mdin 6.3.4 (27 September 2026, schema 6104). Everything on this page is new in 6.3.4 (schema 6100), and nothing is on until a route is made. Brought up on 5 October 2026 to 6.3.6 (6 October 2026, schema 6108), for what 6.3.6 changes on this page. 6.3.7, the latest release (17 October 2026, schema 6108), is not yet reflected here.
An address - or a whole domain this server hosts - whose mail the server posts to a URL:
parsed, signed, and retried until the endpoint takes it. SendGrid and Mailgun call it inbound
parse, and it is what an application actually wants from a mail server: a helpdesk that opens a
ticket from what support@ receives, a CRM that files what orders@ is sent, a service that reads
the replies to reply+<token>@. Until 6.3.4 the only way to have it was a rule that ran a script
(Rules and Sieve). The outbound webhooks tell another program what this server's mail did
(Webhooks.md);
this hands it the mail.
flowchart LR
S["A message for<br/>support@example.com"] --> R{"A route for the address,<br/>or its domain?"}
R -->|"no"| N["Delivered as always"]
R -->|"yes"| Q["After the global rules, the virus scan<br/>and the sandbox: a copy queued<br/>for the route"]
Q --> P["POST to the URL,<br/>form or JSON, signed"]
P -->|"2xx"| OK["Delivered; the copy removed"]
P -->|"anything else"| RT["Retried: 0, 1, 5, 15,<br/>then 60 minutes"]
RT -->|"attempts run out"| F{"on_failure"}
F -->|"keep"| K["Kept, to download,<br/>retry or remove"]
F -->|"bounce"| B["Returned to its sender"]
R -->|"deliver: true"| N
- A route
- Which route an address takes, and what it replaces
- What the endpoint receives
- Retries, and what is kept
- Where an endpoint may be
- Who may
- What it does not do
Made over REST or on the Control Deck's Inbound webhooks view. There is no COM member, no Control Panel page and no setting of its own, as for single sign-on.
POST /api/v1/inbound-webhooks
{
"address": "support@example.com",
"url": "https://helpdesk.example/hooks/mail",
"secret": "a-long-random-string"
}
| Field | What it is | Default |
|---|---|---|
address |
user@example.com, or example.com for every address of the domain; the domain must be one this server hosts |
required |
url |
https:// to the endpoint; plain http:// only to this machine |
required |
secret |
Write-only: every request is signed under it, and it is never returned | required |
deliver |
false: the route alone receives the address's mail. true: what the address reaches without the route - its mailbox, an alias's target, a list - receives it as well |
false |
on_failure |
keep or bounce: what a message the endpoint never accepted becomes |
keep |
format |
form (multipart/form-data in SendGrid's field names) or json
|
form |
attachments |
include (carried as files), names (listed without their bytes) or none
|
include |
include_raw |
Also send the message as it arrived | false |
max_size_kb |
A larger message is not posted, but kept or returned as on_failure says; at most 81920 |
10240 |
enabled |
A route switched off is as though it were not there for new mail | true |
The Deck lists the routes - the address, whether it posts only or posts and delivers, the URL - and the last twenty-five deliveries with the reason one has not arrived; a kept message has Download, Retry and Remove.
An address route wins over its domain's. A recipient with a plus tag (reply+1234@example.com) is
found by the address as written, then by the address before the tag, then by the domain - and the
tag is kept in what is posted, which is usually the point of it.
A route decides that the address exists. RCPT TO is accepted for a routed address whether or
not an account, an alias or a list has it, and a route is asked before any of them
(Aliases Lists and Public Folders):
- with
deliveroff, the route takes the address's mail instead of whatever has the address. A list whose address a route takes never sees the message, nor applies its posting rules to it - so route a list's domain with care, or route the list addresses you mean (Mailing Lists); - with
deliveron, the message is posted and delivered as the address would have been without the route.
A message to several addresses of one route is posted once, with every one of them in its envelope.
A POST, signed, with X-hMailServer-Event: message.inbound and X-hMailServer-Delivery - the
same on every attempt, so a receiver can tell a retry from a second message.
-
format: form:multipart/form-datain the field names SendGrid's Inbound Parse posts -headers,from,to,cc,subject,text,html,envelope,sender_ip,SPF,dkim,charsets,attachments,attachment-infoand the attachments asattachment1onward - so a receiver written for SendGrid reads it, with a few fields of this server's own beside them (spam,dmarc,route,queue_id). Mailgun's field names are not sent. -
format: json: one JSON document of this server's own shape - the envelope, the header fields, the text and HTML bodies, the verdicts, the attachments with their content in base64.
The SPF, DKIM and DMARC results are the ones this server wrote into its own
Authentication-Results field, and only while AuthenticationResultsEnabled is on; a field somebody
else wrote is never read. The spam verdict is this server's own.
Signing is exactly the outbound webhooks' recipe: HMAC-SHA256 over the timestamp, a full stop and
the raw body, under the route's secret, in X-hMailServer-Signature. A receiver that verifies one
verifies the other; verify against the bytes received, in constant time, and refuse an old timestamp.
To rotate the secret, PUT the new one and accept either signature while the change is made.
The message is taken after the global rules, the virus scan and the sandbox have had it, and a
copy is queued; nothing is posted from the delivery thread, and an endpoint hears of it in seconds.
Any 2xx delivers. Anything else - a 4xx, a 5xx, a redirect, no answer - is a failure, retried
on the outbound webhooks' schedule: WebhookMaxAttempts attempts (5 by default), waiting 0, 1,
5, 15 and then 60 minutes. An endpoint that is down holds up only itself.
When the attempts run out, the route's on_failure decides:
-
keep(the default): the message is kept, never swept, to be downloaded, retried or removed, and the error log says so once (HM7401). -
bounce: the message is returned to its sender with an ordinary delivery-status report saying the application that receives the address's mail did not accept it - without repeating what the endpoint answered. Where the sender cannot be told, it is kept instead.
Delivered and returned rows are removed after WebhookRetentionDays (7 by default). The queue
survives a restart. The erasure of an address's personal data sweeps this queue too
(Compliance and eDiscovery).
The endpoint is reached from this server, so where it may be depends on who made the route:
-
The server's administrator may name an address only this server can reach - a receiver on the
same network is the common case - but never a link-local or cloud metadata address
(
169.254.0.0/16,fe80::/10and the clouds' own metadata addresses), which hand out the machine's own credentials. - Every other credential - an API key of any scope or role, or an account whose role is not the server's administrator - may name a public address only: loopback, the private ranges and the reserved ranges are refused.
Both are judged when the route is saved and again at every connection, by what the name resolves
to then. No redirect is followed. Since 6.3.6 a route held to public addresses - one that
anybody but the server's administrator made - posts through HttpProxy when one is set, asking
the proxy for a tunnel to the address the server checked and never to the name (over https
only); a route the server's administrator made, which may reach this server's own network, posts
directly. Up to 6.3.5 no route went through HttpProxy. Plain http is taken only to this machine.
| To | Needs |
|---|---|
| Read the routes and the queue |
view_configuration - a route's URL whole only beside manage_inbound_webhooks; anybody else sees its scheme and host |
| Read a kept message |
view_configuration and view_mailbox
|
| Make, change or remove a route; retry or remove a delivery | manage_inbound_webhooks |
manage_inbound_webhooks is held by the server administrator's role alone among the built-in
roles (Sign-in and Identity §7): routing a domain's mail to a URL is as much as reading all of
it. It is meant for an API key's role - an application that receives mail is given a key whose
role holds it, confined to its own domain, and routes that domain's addresses and no other. Routes
made, changed or removed are on the audit trail, their secrets and URLs recorded as (changed); a
kept message downloaded, retried or removed is recorded as an event. Nothing secret reaches a log.
- No COM and no Control Panel: the REST API and the Deck.
-
No setting of its own: the attempts and the retention are the outbound webhooks', and
WebhooksEnabledsilences the outbound webhooks and not these. - No redirect followed, and no proxy used.
- One post per route per message, not one per recipient.
-
Not in the configuration backup: export the routes with
GET /api/v1/inbound-webhooks- the secrets are in no read. - Mailgun's field names are not sent; SendGrid's are, and the JSON form is this server's own.
The full document, with every field, header, error code and test, is InboundWebhooks.md.
See also: Routes and Relays · Rules and Sieve · Aliases Lists and Public Folders · The REST API · The Control Deck
hMailServer 6.3.7, the latest release (17 October 2026, schema 6108) · AGPL-3.0-or-later · Repository · Mirror on GitHub · Report a documentation error (a GitLab issue naming the page)
Home — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- Profiles and Environment
- The Control Panel
- The Control Deck
- Your First Domain and Mailbox
- Connecting a Mail Client
- The Webmail
- DNS for Your Domain
- Migration
2. Secure it
- Encryption and Certificates
- Database TLS
- Encryption for People
- Stopping Spam
- Stopping Viruses
- Security Hardening
- Secure Defaults
- The Service Account
- Secrets at Rest
- Sign-in and Identity
- Security Policy
3. Operate it
- Monitoring and Health
- The SNMP Agent
- Backup and Restore
- Message Storage
- Compliance and eDiscovery
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- The Update Feed
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Rooms and Equipment
- Mailing Lists
- Routes and Relays
- Mail In to a Webhook
- Outbound Shaping
- The COM API and Scripting
- Event Scripts
- The REST API
- Command Line
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey
- Settings Reference
- Settings Overview
- Capabilities and Configuration
- Control Panel Reference
- Control Panel Pages
- Ports Reference
- Changes-Since-6210
- Version-History-to-6210
- Glossary
About this wiki
- hMailServer 6.3.7 is the latest release (17 October 2026, schema 6108)
- Pages say since 6.3.4 where a feature is new in it
- AGPL-3.0-or-later
- Repository, the one place the project is worked on
- Its mirror on GitHub, since 18 October 2026, with no Issues
- Questions: a GitLab issue with the Question template, GitHub Discussions Q&A, or the forum
- Report a documentation error: a GitLab issue naming the page