NornWeave Quickstart Tutorial
This guide walks you through creating your first inbox, receiving an email, and sending a reply.
Prerequisites
- NornWeave is installed and running
- You have your API key from the configuration
- ngrok installed (for exposing your local server to webhooks)
Expose Your Local Server
Email providers need a public URL to send webhooks. Use ngrok to expose your local NornWeave server:
ngrok http 8000You’ll see output like:
Forwarding https://abc123.ngrok-free.app -> http://localhost:8000https://abc123.ngrok-free.app). You’ll use this to configure webhooks in your email provider.Keep ngrok running in a separate terminal while you work through this guide.
Create an Inbox
EMAIL_DOMAIN is set in your .env file. This is the domain your email provider uses for sending and receiving (e.g., mail.yourdomain.com for Mailgun, or yourdomain.resend.app for Resend). Without it, inbox creation will return a 422 error. See Configuration for details.Create a virtual inbox for your AI agent:
curl -X POST http://localhost:8000/v1/inboxes \
-H "Content-Type: application/json" \
-d '{
"name": "Support Agent",
"email_username": "support"
}'Response:
{
"id": "ibx_abc123",
"name": "Support Agent",
"email_address": "support@mail.yourdomain.com",
"created_at": "2025-01-31T12:00:00Z"
}{email_username}@{EMAIL_DOMAIN}. The email_username becomes the local part before the @, and EMAIL_DOMAIN from your .env provides the domain.Configure Webhook
Point your email provider’s inbound webhook to your ngrok URL:
| Provider | Webhook URL |
|---|---|
| Mailgun | https://abc123.ngrok-free.app/webhooks/mailgun |
| SendGrid | https://abc123.ngrok-free.app/webhooks/sendgrid |
| AWS SES | https://abc123.ngrok-free.app/webhooks/ses |
abc123.ngrok-free.app with your actual ngrok URL. The URL changes each time you restart ngrok (unless you have a paid plan with reserved domains).See Provider Guides for detailed provider-specific setup instructions.
Receive an Email
When someone sends an email to your inbox address, NornWeave:
- Receives the webhook from your provider
- Parses the HTML into clean Markdown
- Groups the message into a thread
- Stores it in the database
List Messages
View all messages in your inbox:
curl "http://localhost:8000/v1/messages?inbox_id=ibx_abc123"Response:
{
"items": [
{
"id": "msg_001",
"thread_id": "th_123",
"inbox_id": "ibx_abc123",
"direction": "inbound",
"content_clean": "Hi, I have a question about your pricing.",
"metadata": {
"From": "Alice <alice@example.com>",
"Subject": "Pricing Question"
},
"created_at": "2025-01-31T10:00:00Z"
}
],
"count": 1
}direction to distinguish between inbound (received) and outbound (sent) emails. The content_clean field contains the message body converted to Markdown.Search Messages
Search for messages containing specific text:
curl -X POST "http://localhost:8000/v1/search" \
-H "Content-Type: application/json" \
-d '{
"inbox_id": "ibx_abc123",
"query": "pricing"
}'Response:
{
"items": [
{
"id": "msg_001",
"thread_id": "th_123",
"inbox_id": "ibx_abc123",
"direction": "inbound",
"content_clean": "Hi, I have a question about your pricing.",
"created_at": "2025-01-31T10:00:00Z"
}
],
"count": 1,
"query": "pricing"
}thread_id from the results to reply to a specific conversation.Send an Email
Send a new email (creates a new thread):
curl -X POST http://localhost:8000/v1/messages \
-H "Content-Type: application/json" \
-d '{
"inbox_id": "ibx_abc123",
"to": ["alice@example.com"],
"subject": "Welcome to our service!",
"body": "Hi Alice!\n\nThanks for signing up. Let us know if you have any questions."
}'Response:
{
"id": "msg_002",
"thread_id": "th_456",
"provider_message_id": "<abc123@mail.yourdomain.com>",
"status": "sent"
}Read a Thread
Retrieve a conversation thread (optimized for LLM context):
curl http://localhost:8000/v1/threads/th_123Response:
{
"id": "th_123",
"subject": "Re: Pricing Question",
"messages": [
{
"role": "user",
"author": "bob@gmail.com",
"content": "Hi, how much does your service cost?",
"timestamp": "2025-01-31T10:00:00Z"
},
{
"role": "assistant",
"author": "support@mail.yourdomain.com",
"content": "Thanks for reaching out! Our pricing starts at $20/month.",
"timestamp": "2025-01-31T10:05:00Z"
}
]
}role: "user" for incoming emails and role: "assistant" for outgoing emails, making it easy to use with LLM chat APIs.Reply to a Thread
To reply to an existing conversation, include the reply_to_thread_id:
curl -X POST http://localhost:8000/v1/messages \
-H "Content-Type: application/json" \
-d '{
"inbox_id": "ibx_abc123",
"reply_to_thread_id": "th_123",
"to": ["alice@example.com"],
"subject": "Re: Pricing Question",
"body": "Hi there!\n\nThanks for your interest! Our pricing starts at **$20/month** for the basic plan."
}'Response:
{
"id": "msg_003",
"thread_id": "th_123",
"provider_message_id": "<def456@mail.yourdomain.com>",
"status": "sent"
}In-Reply-To, References) so replies appear correctly in the recipient’s email client.Using MCP
If you’re using Claude, Cursor, or another MCP-compatible client, you can interact with NornWeave directly.
First, install MCP support:
pip install nornweave[mcp]Add to your MCP configuration:
{
"mcpServers": {
"nornweave": {
"command": "nornweave",
"args": ["mcp"],
"env": {
"NORNWEAVE_API_URL": "http://localhost:8000"
}
}
}
}Then use natural language:
“Check my support inbox for new messages” “Reply to the pricing question thread saying we offer a 14-day free trial”
See MCP Integration for more details.