AgentPost
Guides

Sending and Receiving Email

Complete guide to the email lifecycle in AgentPost

This guide covers the full email lifecycle: creating inboxes, sending messages, handling inbound email, processing attachments, and implementing reply and forward patterns. By the end, you will have a complete understanding of how to build an email-powered agent.

The email lifecycle

Create Inbox --> Send Email --> Receive Replies --> Process & Respond
                    |                |
                    v                v
              SES delivers      SES receives
              to recipient      via SNS notification

Creating an inbox

Every email workflow starts with an inbox. Create one with a descriptive username:

import AgentPost from 'agentpost';

const client = new AgentPost({ apiKey: 'ap_sk_...' });

const inbox = await client.inboxes.create({
  username: 'support',
  display_name: 'AcmeCo Customer Support',
  client_id: 'support-inbox-prod', // Idempotent -- safe to retry
});

console.log(`Support inbox: ${inbox.email}`);
from agentpost import AgentPost

client = AgentPost(api_key="ap_sk_...")

inbox = client.inboxes.create(
    username="support",
    display_name="AcmeCo Customer Support",
    client_id="support-inbox-prod",
)

print(f"Support inbox: {inbox.email}")
curl -X POST https://api.agent-post.dev/api/v1/inboxes \
  -H "Authorization: Bearer $AGENTPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "support",
    "display_name": "AcmeCo Customer Support",
    "client_id": "support-inbox-prod"
  }'

Sending email

Basic send

const message = await client.messages.send(inbox.id, {
  to: [{ email: '[email protected]', name: 'Jane Smith' }],
  subject: 'Your support ticket #4821 has been received',
  text_body: 'Hi Jane,\n\nWe have received your support request and a team member will respond within 2 hours.\n\nTicket ID: #4821\nSubject: Login issues on mobile app\n\nBest,\nAcmeCo Support',
  html_body: '<p>Hi Jane,</p><p>We have received your support request and a team member will respond within 2 hours.</p><p><strong>Ticket ID:</strong> #4821<br><strong>Subject:</strong> Login issues on mobile app</p><p>Best,<br>AcmeCo Support</p>',
});
message = client.messages.send(
    inbox_id=inbox.id,
    to=[{"email": "[email protected]", "name": "Jane Smith"}],
    subject="Your support ticket #4821 has been received",
    text_body="Hi Jane,\n\nWe have received your support request...",
    html_body="<p>Hi Jane,</p><p>We have received your support request...</p>",
)
curl -X POST "https://api.agent-post.dev/api/v1/inboxes/$INBOX_ID/messages" \
  -H "Authorization: Bearer $AGENTPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": [{"email": "[email protected]", "name": "Jane Smith"}],
    "subject": "Your support ticket #4821 has been received",
    "text_body": "Hi Jane, We have received your support request..."
  }'

With CC and BCC

const message = await client.messages.send(inbox.id, {
  to: [{ email: '[email protected]', name: 'Jane Smith' }],
  cc: [{ email: '[email protected]', name: 'Team Lead' }],
  bcc: [{ email: '[email protected]' }],
  subject: 'Resolution: Ticket #4821',
  text_body: 'Hi Jane, the login issue has been resolved...',
});
message = client.messages.send(
    inbox_id=inbox.id,
    to=[{"email": "[email protected]", "name": "Jane Smith"}],
    cc=[{"email": "[email protected]", "name": "Team Lead"}],
    bcc=[{"email": "[email protected]"}],
    subject="Resolution: Ticket #4821",
    text_body="Hi Jane, the login issue has been resolved...",
)
curl -X POST "https://api.agent-post.dev/api/v1/inboxes/$INBOX_ID/messages" \
  -H "Authorization: Bearer $AGENTPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": [{"email": "[email protected]", "name": "Jane Smith"}],
    "cc": [{"email": "[email protected]", "name": "Team Lead"}],
    "bcc": [{"email": "[email protected]"}],
    "subject": "Resolution: Ticket #4821",
    "text_body": "Hi Jane, the login issue has been resolved..."
  }'

Receiving email

Polling for new messages

The simplest approach is to periodically poll for new inbound messages:

async function pollForMessages(inboxId: string) {
  const messages = await client.messages.list(inboxId, {
    direction: 'inbound',
    limit: 10,
  });

  for (const msg of messages.data) {
    console.log(`New message from ${msg.from_address.email}`);
    console.log(`Subject: ${msg.subject}`);
    console.log(`Content: ${msg.extracted_text}`);

    // Process the message (classify, respond, label, etc.)
    await processInboundMessage(msg);
  }
}
def poll_for_messages(inbox_id: str):
    messages = client.messages.list(
        inbox_id=inbox_id,
        direction="inbound",
        limit=10,
    )

    for msg in messages.data:
        print(f"New message from {msg.from_address['email']}")
        print(f"Subject: {msg.subject}")
        process_inbound_message(msg)
curl "https://api.agent-post.dev/api/v1/inboxes/$INBOX_ID/messages?direction=inbound&limit=10" \
  -H "Authorization: Bearer $AGENTPOST_API_KEY"

Real-time with webhooks

For production use, configure a webhook endpoint to receive instant notifications:

// 1. Create a webhook endpoint
const endpoint = await client.webhookEndpoints.create({
  url: 'https://your-agent.example.com/webhooks/agentpost',
  events: ['message.received'],
});

// 2. Handle the webhook in your server
app.post('/webhooks/agentpost', async (req, res) => {
  const event = req.body;

  if (event.type === 'message.received') {
    const message = event.data;
    console.log(`New email from ${message.from_address.email}`);

    // Auto-classify and respond
    const classification = await classifyMessage(message.extracted_text);
    await client.messages.addLabels(message.id, {
      labels: [classification.category],
    });

    if (classification.auto_reply) {
      await client.messages.reply(message.id, {
        text_body: classification.response,
      });
    }
  }

  res.status(200).json({ received: true });
});
# 1. Create a webhook endpoint
endpoint = client.webhook_endpoints.create(
    url="https://your-agent.example.com/webhooks/agentpost",
    events=["message.received"],
)

# 2. Handle the webhook in your server (Flask example)
@app.route("/webhooks/agentpost", methods=["POST"])
def handle_webhook():
    event = request.json

    if event["type"] == "message.received":
        message = event["data"]
        classification = classify_message(message["extracted_text"])

        client.messages.add_labels(
            message_id=message["id"],
            labels=[classification["category"]],
        )

    return {"received": True}, 200
# Create a webhook endpoint
curl -X POST https://api.agent-post.dev/api/v1/webhook-endpoints \
  -H "Authorization: Bearer $AGENTPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-agent.example.com/webhooks/agentpost",
    "events": ["message.received"]
  }'

Reply and forward patterns

Replying to a message

Replies stay in the same thread and automatically set the correct email headers:

// Reply adds "Re:" prefix and continues the thread
const reply = await client.messages.reply(inboundMessage.id, {
  text_body: 'Hi Jane,\n\nI have looked into the login issue. The root cause was an expired session token. I have reset your session -- please try logging in again.\n\nLet me know if this resolves the issue.\n\nBest,\nAcmeCo Support',
});
reply = client.messages.reply(
    message_id=inbound_message.id,
    text_body="Hi Jane,\n\nI have looked into the login issue...",
)
curl -X POST "https://api.agent-post.dev/api/v1/messages/$MESSAGE_ID/reply" \
  -H "Authorization: Bearer $AGENTPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text_body": "Hi Jane, I have looked into the login issue..."}'

Forwarding a message

Forwarding creates a new thread with the original message content:

// Forward creates a new thread with "Fwd:" prefix
const forwarded = await client.messages.forward(inboundMessage.id, {
  to: [{ email: '[email protected]', name: 'Mobile Team' }],
  text_body: 'Can the mobile team investigate this login issue? Customer reports it only happens on iOS 18.',
});
forwarded = client.messages.forward(
    message_id=inbound_message.id,
    to=[{"email": "[email protected]", "name": "Mobile Team"}],
    text_body="Can the mobile team investigate this login issue?",
)
curl -X POST "https://api.agent-post.dev/api/v1/messages/$MESSAGE_ID/forward" \
  -H "Authorization: Bearer $AGENTPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": [{"email": "[email protected]", "name": "Mobile Team"}],
    "text_body": "Can the mobile team investigate this login issue?"
  }'

Processing attachments

Handling inbound attachments

// Check if the message has attachments
if (message.has_attachments) {
  // Get attachment details
  const attachment = await client.attachments.get(message.attachment_ids[0]);

  // Download the file
  const response = await fetch(attachment.download_url);
  const buffer = await response.arrayBuffer();

  // Process the attachment (e.g., extract text from PDF)
  const text = await extractTextFromPDF(Buffer.from(buffer));
  console.log(`Extracted ${text.length} characters from ${attachment.filename}`);
}
if message.has_attachments:
    attachment = client.attachments.get(message.attachment_ids[0])

    import requests
    response = requests.get(attachment.download_url)

    # Process the attachment
    text = extract_text_from_pdf(response.content)
    print(f"Extracted {len(text)} characters from {attachment.filename}")
# Get attachment details
curl "https://api.agent-post.dev/api/v1/attachments/$ATTACHMENT_ID" \
  -H "Authorization: Bearer $AGENTPOST_API_KEY"

# Download from the presigned URL (returned in the response)
curl -o "downloaded-file.pdf" "$DOWNLOAD_URL"

Building a support ticket agent

Here is a complete example of a support ticket agent that receives email, classifies it, and responds:

import AgentPost from 'agentpost';

const client = new AgentPost({ apiKey: 'ap_sk_...' });

async function handleSupportTicket(message: any) {
  // 1. Label the message as received
  await client.messages.addLabels(message.id, {
    labels: ['received', 'needs-triage'],
  });

  // 2. Classify the message content
  const category = await classifyWithLLM(message.extracted_text);
  await client.messages.addLabels(message.id, {
    labels: [category], // e.g., "billing", "technical", "account"
  });
  await client.messages.removeLabels(message.id, {
    labels: ['needs-triage'],
  });

  // 3. Generate and send a response
  const response = await generateResponseWithLLM(
    message.extracted_text,
    category,
  );

  await client.messages.reply(message.id, {
    text_body: response,
    html_body: `<p>${response.replace(/\n/g, '<br>')}</p>`,
  });

  // 4. Label as responded
  await client.messages.addLabels(message.id, {
    labels: ['auto-responded'],
  });

  console.log(`Handled ticket from ${message.from_address.email} [${category}]`);
}
from agentpost import AgentPost

client = AgentPost(api_key="ap_sk_...")

def handle_support_ticket(message):
    # 1. Label as received
    client.messages.add_labels(
        message_id=message.id,
        labels=["received", "needs-triage"],
    )

    # 2. Classify
    category = classify_with_llm(message.extracted_text)
    client.messages.add_labels(message_id=message.id, labels=[category])
    client.messages.remove_labels(message_id=message.id, labels=["needs-triage"])

    # 3. Generate and send response
    response = generate_response_with_llm(message.extracted_text, category)
    client.messages.reply(
        message_id=message.id,
        text_body=response,
    )

    # 4. Mark as responded
    client.messages.add_labels(message_id=message.id, labels=["auto-responded"])
# This workflow is best implemented in code using the SDK.
# The cURL equivalent would require multiple sequential requests:

# 1. Label as received
curl -X POST "https://api.agent-post.dev/api/v1/messages/$MSG_ID/labels" \
  -H "Authorization: Bearer $AGENTPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"labels": ["received", "needs-triage"]}'

# 2. Reply to the message
curl -X POST "https://api.agent-post.dev/api/v1/messages/$MSG_ID/reply" \
  -H "Authorization: Bearer $AGENTPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text_body": "Thank you for contacting support..."}'

Tips

  • Always provide both text_body and html_body -- some email clients only render one format
  • Use extracted_text (not html_body) for AI processing of inbound messages
  • Subject prefixes (Re:, Fwd:) are added automatically -- do not add them yourself
  • Use client_id on inbox creation to safely handle agent restarts
  • For high-volume workflows, use webhooks instead of polling
  • Check send_paused on your inbox before sending -- it indicates a delivery issue

On this page