📥 Inbound Messages

Pingram can capture inbound emails and SMS, and deliver them to your webhook endpoint, with automatic reply detection and tracking.

Overview

Pingram provides a complete inbound messaging solution:

  1. Your domain - After you verify a domain and point its inbound MX at Pingram, every address on that domain and its subdomains is delivered to your webhook. Saved addresses are not required.
  2. SMS replies - Replies to your SMS are delivered as SMS_INBOUND
  3. Webhooks - Process incoming messages via webhooks
  4. Reply Detection - Automatically detect if a message is a reply and link it to the original thread
  5. Inbox Preview - APIs and Dashboard tools to view inbound messages

Receiving on your domain

Verify your domain in Settings > Domains and add the inbound MX record shown there. Pingram then accepts mail for every recipient on that domain and its subdomains. support@yourcompany.com and scheduler@mail.yourcompany.com are both delivered when yourcompany.com is DKIM-verified. Adding a domain before DKIM succeeds does not deliver its mail. You do not create an address for each mailbox.

matched is the address on your account that received the mail, including when that address was only in Cc or Bcc. to and cc are deprecated. Use toRecipients and ccRecipients.

Saved addresses are for trying inbound email before you have a domain. Each account has one at yourcompany@mail.pingram.io. You can change the prefix in the Dashboard. Do not use these addresses for production traffic.

INFO

A Pingram address works immediately. No DNS configuration is required. Your own domain needs the inbound MX record.


Email Inbound Webhook

Webhook Payload

When an email is received, your webhook receives a POST request with the following JSON payload:

{
eventType: 'EMAIL_INBOUND',
from: string, // Sender's email address
fromName?: string, // Sender display name
matched: {
email: string, // Address on your account that received the mail
name?: string // Display name, when the headers include one
},
to?: string, // @deprecated First To header address only. Use toRecipients.
cc?: string[], // @deprecated Cc header addresses, without display names. Use ccRecipients.
bcc?: string[], // Legacy. Bcc header addresses still on the message. Usually absent
toRecipients?: Array<{ email: string, name?: string }>, // Every To header address
ccRecipients?: Array<{ email: string, name?: string }>, // Every Cc header address
bccRecipients?: Array<{ email: string, name?: string }>, // Bcc header, when it is still on the message
replyTo?: string,
subject: string, // Email subject line
bodyText?: string, // Plain text body (if available)
bodyHtml?: string, // HTML body (if available)
attachments?: Array<{
filename: string,
contentType: string,
size: number,
content: string, // Base64-encoded content
contentId?: string, // Content-ID without angle brackets; matches cid: in bodyHtml
contentDisposition?: 'inline' | 'attachment'
}>,
receivedAt: string, // When the message was accepted, ISO 8601
sentAt?: string, // Sender Date header, ISO 8601. Absent when that header is missing or invalid
messageId?: string, // Original Message-ID header
inReplyTo?: string, // In-Reply-To header for threading
references?: string, // References header for threading
trackingId: string, // Stable id for this inbound email. Replies reuse the original notification's tracking ID
userId?: string, // User ID from the original notification, when this is a reply
type?: string // Notification type from the original notification, when this is a reply
}

matched.email is your address that received the mail. matched.name is its display name when the headers include one. Compare matched.email with toRecipients, ccRecipients, and bccRecipients to see which header contained it. bccRecipients is present only when the Bcc header is still on the message. A reply uses toRecipients and ccRecipients. to and cc are deprecated and unchanged for existing webhooks. to is the first To-header address. cc is the Cc addresses without display names.

receivedAt is when Pingram accepted the message. sentAt is the sender’s Date header and is omitted when that header is missing or not a valid date. Log order uses receivedAt.

Size limits

The whole inbound message, including headers, can be up to 40 MB. There is no separate per-attachment cap. Mail larger than 40 MB is bounced before Pingram stores it.

Each file is base64 in attachments[].content, so the webhook JSON is about one third larger than the raw message. A full-size message needs a body parser that accepts that JSON. A host with a smaller request-body cap, such as Vercel’s 4.5 MB limit, can reject a webhook Pingram already accepted. Host the handler where the JSON fits, or keep attachments under that platform’s limit.

Sending uses different limits: about 4 MB raw per inline file, and up to 20 MB for a URL attachment. See Attachment size limits.

A failed EMAIL_INBOUND webhook can be resent for 30 days with logs.retryInbound (POST /logs/retry/{trackingId}) or the Resend webhook button on the inbound row in Logs. The resend uses the same trackingId and does not count as another email.

Reply Detection

Pingram automatically detects replies in two ways:

  1. Reply-to tracking: When you send notifications, we set a unique reply-to address that lets us link replies to the original message
  2. Thread matching: We analyze email headers and content to identify replies even when users don’t use the reply-to address

SMS Inbound Webhook

Webhook Payload

When an SMS is received, your webhook receives a POST request with the following JSON payload. STOP and START are not included here: they arrive as SMS_UNSUBSCRIBE and SMS_SUBSCRIBE.

{
eventType: 'SMS_INBOUND',
from: string, // Phone number of the sender
to: string, // Your Pingram SMS number
text: string, // Message content
receivedAt: string, // ISO 8601 timestamp
// Reply tracking (only present if this is a reply to a previous message)
isReply: boolean, // true if linked to a previous outbound message
userId?: string, // The user's ID in your system (if isReply)
lastTrackingId?: string // Tracking ID of your last outbound message to this user (if isReply)
}

Costs

Inbound messages count towards your email and SMS usage. One processed inbound email counts as 1 email. Attachment size does not add usage.

Example:

- Your software sends 1 SMS segment to user A -> 1 SMS usage
- User replies to the SMS -> 1 SMS usage
Total usage: 2 SMS usage

Logs

All inbound messages are logged in your Logs dashboard.

API Access

In addition to the webhooks, you can programmatically access your inbound messages via the logs list API.