Tutorials12 min read

Building an AI email agent with Programmable Inbox

A step-by-step guide to wiring up an LLM that reads, classifies, and auto-replies to inbound email using our API and webhooks.

PN

Priya Nair

Developer Relations · July 2, 2025

One of the most exciting use cases for Programmable Inbox is building AI agents that handle email. In this tutorial we'll wire up a simple agent that:

  1. 1Receives inbound email via webhook
  2. 2Classifies it (support request, billing question, spam)
  3. 3Auto-replies to support requests using an LLM

What you'll need

  • A Programmable Inbox account (or local self-hosted instance)
  • Node.js 20+
  • An OpenAI API key

Step 1: Create an inbox

bash
curl -X POST https://api.programmableinbox.dev/inboxes \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"name": "support", "domain": "acme.com"}'

Note the inbox ID and the address you receive back ([email protected] in sandbox mode).

Step 2: Register a webhook

bash
curl -X POST https://api.programmableinbox.dev/inboxes/{id}/webhooks \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"url": "https://your-app.com/api/email-webhook", "events": ["message.received"]}'

Step 3: Handle the webhook

ts
// app/api/email-webhook/route.ts
import { NextRequest, NextResponse } from 'next/server'
import OpenAI from 'openai'

const client = new OpenAI()

export async function POST(req: NextRequest) {
  const { message } = await req.json()

  const classification = await client.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      {
        role: 'system',
        content: 'Classify this email as: support, billing, or spam. Reply with one word.',
      },
      { role: 'user', content: message.body.text },
    ],
  })

  const category = classification.choices[0].message.content?.toLowerCase()

  if (category === 'support') {
    const reply = await client.chat.completions.create({
      model: 'gpt-4o',
      messages: [
        {
          role: 'system',
          content: 'You are a helpful support agent for Acme Corp. Reply concisely and helpfully.',
        },
        { role: 'user', content: message.body.text },
      ],
    })

    await fetch(`https://api.programmableinbox.dev/messages/${message.id}/reply`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.API_KEY}` },
      body: JSON.stringify({ text: reply.choices[0].message.content }),
    })
  }

  return NextResponse.json({ ok: true })
}

Going further

This is a minimal example. In production you'd want to add:

  • Signature verification on the webhook payload
  • Rate limiting to avoid runaway LLM costs
  • A human-in-the-loop review step for low-confidence classifications
  • Thread context — pass the full conversation history to the LLM, not just the latest message

All of that is doable with the Programmable Inbox API. The full thread history for any inbox is available at GET /inboxes/{id}/threads/{threadId}/messages.