MX records decide where inbound mail for a host actually goes. Point the wrong host at an app and your team's mailboxes stop arriving. This lesson covers how MX works, the two PostShiba inbox kinds, and how replies reach the right conversation.
Pick an inbox type, a host, and a delivery mode below. The MX record and a sample payload update as you go.
Route inbound mail.
Pick a host and a delivery path.
The address lives on the PostShiba inbound zone. There is no DNS to publish.
support-acme@your-inbound-zone
Only if this host has no MX records today. PostShiba refuses a host that already has MX.
Delivery
reply+ticket_123@inbound.example.com lands in the same inbox. The tag is available as envelope.plus_tag.
What MX records do
MX tells the world which server accepts mail for a host. Each record has a priority. Lower numbers are tried first. If the lowest-priority host does not answer, senders try the next one.
Senders try mail.example.com first. They try mail-backup.example.com only if that host will not accept the message.
Your root domain almost certainly already has MX at Google, Microsoft, or whoever hosts you@example.com. People write to that address and that host answers. PostShiba never asks you to change those records.
Do not point root MX at an app. Do not point it at PostShiba either, unless that domain has no mailboxes today. The team's inbox lives on the root. Point those records away and payroll, support, and founder mail miss the mailbox host they already use. That is a bad afternoon and a hard one to unwind.
MX is an inbound record. It is not part of SPF, DKIM, or DMARC. Sending from a subdomain such as mail.example.com leaves root MX free. The sending host can have no MX at all.
Receive on a subdomain
Receive on a host that has no mail today, such as inbound.example.com. Leave root MX with the mailbox host.
The default PostShiba host is inbound. plus your sending domain. For example.com that is inbound.example.com. For mail.example.com that is inbound.mail.example.com. You can change the prefix. support.example.com works when that host has no MX yet. The host must be the sending domain or a subdomain of it, and it cannot be rp.{sending_domain}.
Use the root only when the domain has no mail today, for example when PostShiba will receive every message for it.
PostShiba checks for you. When you create a domain-bound inbox, it refuses any host that already has MX records, so you cannot take over a live mailbox by accident. See How do I avoid MX conflicts?.
Publish the MX record shown on the inbox. It points that host at the PostShiba inbound server with priority 10. Publish it on that host only. Then select Check MX. Mail reaches the inbox once the record resolves.
Some mailbox hosts treat every subdomain as their own. Fastmail does this by default. A sender on the same host then gets a user-unknown bounce, and the message never reaches PostShiba. Senders on other hosts follow your MX and succeed. On Fastmail, set the domain's routing to reject mail to subdomains. Other hosts have a similar "this domain only" setting.
Two kinds of inbox
An inbox gives you an address that receives mail. PostShiba stores each message, and can POST it to a webhook or forward it.
| Type | Address | DNS | Where you create it |
|---|---|---|---|
| Domain-bound | A host under one of your sending domains, such as support@inbound.example.com |
MX on that host | Dashboard or API |
| Hosted | A generated address on the PostShiba inbound zone | None | API only |
Use a domain-bound inbox when people should see your domain on the mailbox. Use a hosted inbox when you need an address before DNS is ready. Hosted inboxes skip MX entirely. The dashboard form only offers domain-bound inboxes.
On the dashboard, open Inbound → Inboxes, then select Issue inbox. Choose the sending domain. Under Where should mail land?, keep A subdomain unless the root has no mail. Type the mailbox, such as support. Use * for a catch-all. You can issue inboxes while the team waits for production approval.
Where the mail goes next
Without a webhook or a forward, PostShiba stores the mail and you read it in the dashboard or over the API. Most apps want the message as it arrives.
Forwarding sends a copy to another address. PostShiba writes a new message and signs it with your domain, so the copy passes authentication at the destination. The copy's From is the inbox mailbox at the forward address's domain. Reply-To is the original sender. The forward address must be on one of your verified sending domains. It cannot loop back to the inbox's own host, and it cannot be another inbox.
A receiving webhook POSTs each inbound message to your URL as soon as it is stored. The URL must be public. In production it must use https.
| Webhook format | API value | Body |
|---|---|---|
| PostShiba JSON | json (default) |
The inbound payload as JSON |
| SendGrid Inbound Parse | sendgrid_parse |
SendGrid's multipart form fields |
The JSON payload includes to, from, subject, text, html, selected headers, the SMTP envelope, authentication results, and attachments. Message show also returns raw with the full MIME. A sendgrid_parse webhook puts that MIME in the email field and sends files as attachment1, attachment2, and so on.
Attachments arrive as metadata plus content_base64 on the JSON webhook and on message show. The list endpoint returns metadata only. You can also download the raw file. PostShiba keeps the same file types it lets you send. Other files are left out. Mail carrying a virus is refused before it reaches your inbox.
Plus-address threads
Plus-addressing routes reply+ticket_123@inbound.example.com to the inbox reply@inbound.example.com and sets thread_id to ticket_123. Give each conversation its own tag so replies land on the right thread.
Without a plus tag, thread_id is the first In-Reply-To value, then the first References value, then the message's own Message-ID. Full fields are on Get email content.
Verify every webhook
Check the signature on every POST before you parse the body. Inbox webhooks use the same headers as delivery webhooks, keyed with the inbox's webhook_secret. Copy that secret from the webhook dialog when you save the URL.
| Header | Value |
|---|---|
X-Capsule-Timestamp |
Unix time, as a string, when the request was signed. |
X-Capsule-Signature |
sha256= followed by the hex HMAC-SHA256 of {timestamp}.{body}. |
Read the raw body before any JSON parsing. Re-serialized JSON will not match. Compute the HMAC-SHA256 of {timestamp}.{body} with the secret, strip sha256= from the header, and compare the two values in constant time. Reject a mismatch with 401. Each attempt, including a retry, is signed with a fresh timestamp. The verify webhooks page has helpers for Node.js and Ruby.
Inbound is free, with a cap
Receiving is free. Inbound mail has one hourly allowance for the whole team, shared across every inbox. It equals the sum of your clusters' hourly send caps. The inbox page shows how much is used this hour.
Over the allowance, inbound SMTP returns 451 hourly inbound limit reached, and the sender retries later. Size clusters for the busiest hour you actually send. The inbound cap follows that total.
That is the shape. Leave root MX with the mailbox host. Receive on inbound.example.com or another empty host. Use a hosted inbox when DNS is not ready. Forward, POST signed JSON, or use SendGrid Inbound Parse. Tag replies so ticket_123 stays on one thread. Verify the signature before you trust the body.
Check what stuck.
A few questions. Two right marks the lesson done.
You finished this lesson.
Lesson complete.
Do this in PostShiba.
The docs walk through each step in the dashboard and the API.
- Receiving emails Domain-bound inboxes in the dashboard, or hosted inboxes over the API.
- Custom receiving domains MX on inbound.your-domain, another subdomain, or the root if it has no MX.
- Receiving webhooks POST signed JSON or SendGrid Inbound Parse.
- How do I avoid MX conflicts? Default to inbound.your-domain. We refuse a host that already has MX.