Create a key to copy examples filled with your team.
Build an email support desk on PostShiba
Receive support mail, keep conversations together, and send replies.
PostShiba gives you the mail plumbing for a support desk. Your application owns tickets, assignment, search, and the agent interface. PostShiba receives the message, parses it, stores it for a configurable retention window, and sends a signed webhook to your application. The default window is 168 hours.
This guide builds a desk with:
help@inbound.support.example.comfor incoming mailhelp@support.example.comfor replies- plus-addresses such as
help+ticket_123@inbound.support.example.comto keep later replies attached to a ticket - delivery webhooks for the messages your agents send
PostShiba puts custom-domain inboxes on
inbound.{sending_domain}. It does not take over the MX records forsupport.example.com. That keeps existing mailboxes on the root domain alone.
The shape of the system
Keep four records in your own database:
- An account or workspace that maps to a PostShiba tenant when you serve more than one company.
- A mailbox that stores the PostShiba inbox ID, address, and webhook secret.
- A ticket keyed by your own ticket ID.
- A message keyed by the PostShiba inbound message ID or outbound
message_id.
Use PostShiba IDs for API calls, but keep your own IDs in URLs and product logic. You can put those IDs in unique_args so delivery events find their way back to the right ticket.
Prepare the shared cluster
PostShiba must approve the team before you can create a cluster or inbox. Both requests return 403 {"error":"kyc_required"} until then.
Create an active shared-IP cluster before you rely on any inbox. PostShiba's shared edge only loads inboxes for approved teams with an active shared cluster.
Wait until sending_ready is true. Hosted inboxes skip DNS, but they still need this cluster to reach the shared edge.
1. Prepare the sending domain
Create and verify support.example.com before you create a domain-bound inbox. The same domain lets your agents reply from help@support.example.com.
The shell examples share these variables. Run the exports in your current shell, or put them in setup.sh and load that file with source setup.sh.
Publish the returned DKIM CNAME and return-path CNAME. SPF is optional. Call the verify action after DNS is visible:
Wait for dkim_status and return_path_status to become verified.
If your support product hosts several companies in one PostShiba team, create one tenant per company and pass its tenant_id when you create the domain. That keeps domains, inboxes, credentials, and suppressions separate.
2. Create the inbox
Set sending_domain_id to receive on your domain. Use the JSON webhook format unless you already have a SendGrid Inbound Parse handler.
PostShiba returns the address, MX record, and webhook secret:
Save webhook_secret now. PostShiba includes it on create and show, but not when you list inboxes.
Publish the returned MX record on inbound.support.example.com. Do not replace the MX record for support.example.com or example.com.
Wait for mx_status to become verified.
3. Accept the inbound webhook
PostShiba signs the raw request body. Read it before parsing JSON. If your framework parses the body first, configure that route to retain the original bytes.
This handler uses the standard Request and Response APIs. Pass in your own database and queue functions.
saveMessage must upsert on the PostShiba message ID. enqueueOnce must use its key as a durable deduplication key. If queue publication fails, let the request fail so PostShiba retries both idempotent steps.
The five-minute limit is your application's replay window. PostShiba signs the timestamp, but your receiver must reject requests that are too old.
Save the message to durable storage before returning 2xx; otherwise, you may acknowledge mail you have not stored. PostShiba retries failed webhooks after five minutes, 30 minutes, two hours, eight hours, and 24 hours. After the last failed attempt, it marks the delivery dead.
4. Turn messages into tickets
The JSON body contains parsed text, HTML, headers, envelope data, and attachment metadata. Attachment rows on the JSON webhook also include content_base64.
Prefer text for search and agent previews. Keep HTML untrusted. Sanitize it before rendering. Scan and limit attachments before an agent opens them.
For a new message to the base inbox, create a ticket in your database. For later messages, look for your ticket ID in thread_id. PostShiba sets that field from the plus tag first, then falls back to standard reply headers and Message-ID.
5. Send an agent reply
Create a shared cluster and an SMTP credential before using /sends. The transactional email guide shows both calls. If you use a tenant for this company, create the credential for that tenant and pass the tenant slug on every send.
Set three headers when you reply:
reply_touses a plus-address with your ticket IDIn-Reply-Topoints at the customer's Message-IDReferencescarries the existing thread references
When the customer replies, mail goes to help+ticket_123@inbound.support.example.com. PostShiba matches it to the help inbox and sets thread_id to ticket_123.
queued: true means at least one recipient was accepted by the injector. This guide sends one recipient per request so each support reply gets a clear queue result.
6. Track the reply
The inbox webhook handles incoming mail. Delivery events for your outgoing replies use a separate webhook endpoint.
Create one endpoint with processed, delivered, deferred, bounce, dropped, and spamreport. Match its ticket_id and support_message_id unique args to your database. A delivered event means the destination accepted the reply. It does not prove the customer read it.
Keep a polling fallback
PostShiba retains inbound messages even when you use a webhook. The default is 168 hours. Read retention_hours from the inbox and expires_at from each message instead of assuming the default. That gives you a recovery path:
There is no replay action for a dead inbound inbox delivery. Poll the message API before expires_at. Replay in the dashboard only applies to outbound delivery-event webhooks.
The list returns the latest 200 live messages. Fetch one message to include raw MIME and attachment bytes:
Copy mail into your own long-term storage. PostShiba's inbox is a short recovery window, not your ticket archive.
Before you launch the support desk
- Keep the platform application token on your server. It has access to the whole PostShiba team.
- Use one tenant per customer when several companies share your support product.
- Use one inbox per public support address. Use plus tags for tickets, not as an access-control boundary.
- Keep ticket IDs in plus-addresses lowercase and email-safe.
- Deduplicate inbound webhooks on
message.id. - Return
2xxafter the durable write, then process routing, spam checks, and automation in a queue. - Sanitize HTML and treat message text as untrusted input.
- Disable an inbox with
DELETE /api/v1/inboxes/:idwhen you retire an address.
The API cannot update an inbox's webhook URL or format. Change those settings in the PostShiba dashboard. The dashboard is also where you replay dead outbound delivery-event webhooks.