Receiving Email

Inbound email processing with webhooks and polling for agent mailboxes.

How Inbound Works

When someone sends an email to your agent's mailbox address, Transactional receives the message and makes it available in two ways:

  1. Webhooks (recommended) — Real-time HTTP POST to your endpoint
  2. Polling — Query the inbox API on a schedule

Inbound emails are available via API in under 2 seconds after receipt.

Webhook Delivery

Set up a webhook to receive real-time notifications when new emails arrive.

// Register a webhook
await tx.agentEmail.webhooks.create({
  mailbox: mailbox.id,
  url: 'https://yourapp.com/webhooks/agent-email',
  events: ['message.received'],
});

Your endpoint receives a POST request with the full message:

// Express/Hono webhook handler
app.post('/webhooks/agent-email', async (req, res) => {
  const event = req.body;
 
  if (event.type === 'message.received') {
    const message = event.data;
    console.log(`New email from ${message.from}`);
    console.log(`Subject: ${message.subject}`);
    console.log(`Body: ${message.textBody}`);
 
    // Process with your agent
    await agent.processIncomingEmail(message);
  }
 
  res.status(200).send('OK');
});

See Webhooks for HMAC verification and all event types.

Polling

Query the inbox API to check for new messages. Useful for batch processing or environments where webhooks are not practical.

// Poll for unread messages
const messages = await tx.agentEmail.inbox.list({
  mailbox: mailbox.id,
  unread: true,
  sort: 'received_at:desc',
  limit: 20,
});
 
for (const message of messages.data) {
  await agent.processIncomingEmail(message);
 
  // Mark as read after processing
  await tx.agentEmail.inbox.markRead(message.id);
}

Webhooks vs Polling

WebhooksPolling
LatencyReal-time (under 2s)Depends on interval
ComplexityRequires public endpointSimple API calls
ReliabilityAutomatic retriesYou control retry logic
Best forConversational agentsBatch processing, cron jobs

Inbound Parsing

Transactional parses inbound emails and extracts:

FieldDescription
fromSender address
toRecipient address(es)
ccCC addresses
subjectEmail subject
htmlBodyHTML content
textBodyPlain text content
headersAll email headers
attachmentsFile attachments with download URLs
threadIdThread ID (if part of a conversation)
inReplyToMessage ID this is replying to
receivedAtTimestamp of receipt

Attachment Handling

Inbound attachments are stored and accessible via download URL.

const message = await tx.agentEmail.inbox.get(messageId);
 
for (const attachment of message.attachments) {
  console.log(`File: ${attachment.filename}`);
  console.log(`Size: ${attachment.size} bytes`);
  console.log(`Type: ${attachment.contentType}`);
 
  // Download the attachment
  const response = await fetch(attachment.downloadUrl);
  const buffer = await response.arrayBuffer();
}

Attachments are retained for 30 days after receipt.

Spam Filtering

Inbound messages are automatically scanned for spam. Messages flagged as spam are still delivered but include a spam score.

if (message.spamScore > 5.0) {
  console.log('Likely spam, skipping');
  await tx.agentEmail.inbox.archive(message.id);
  return;
}

Next Steps