This guide covers the essential workflows for integrating Sendook into your AI agent.
Every account belongs to an Organization. Organizations contain:
An Inbox is a mailbox your AI agent can send and receive from. Each inbox has:
inbox-123@sendook.com)Messages are emails sent or received by an inbox. They include:
Every AI agent needs at least one inbox to communicate:
const inbox = await client.inbox.create({
name: 'Customer Support Agent',
description: 'Handles all customer support emails',
webhookUrl: 'https://your-app.com/webhooks/email'
});
console.log(`Inbox created: ${inbox.email}`);
// Example output: inbox-abc123@sendook.com
Send emails from any inbox:
const message = await client.inbox.message.send({
inboxId: inbox.id,
to: 'customer@example.com',
subject: 'Re: Your Support Request',
body: 'Thanks for reaching out! Here's the solution...',
// Optional fields
cc: ['team@example.com'],
bcc: ['archive@example.com'],
attachments: [
{
filename: 'solution.pdf',
content: Buffer.from(pdfData).toString('base64'),
contentType: 'application/pdf'
}
]
});
Maintain conversation context with automatic threading:
// When replying, include the original message ID
const reply = await client.inbox.message.send({
inboxId: inbox.id,
to: message.from,
subject: `Re: ${message.subject}`,
body: 'Here's my follow-up response...',
inReplyTo: message.id, // Links to original message
threadId: message.threadId // Keeps conversation together
});
Configure your webhook endpoint to receive emails in real-time:
// Express.js example
app.post('/webhooks/email', async (req, res) => {
const event = req.body;
// Verify webhook signature
const signature = req.headers['x-sendook-signature'];
if (!client.webhooks.verify(req.body, signature)) {
return res.status(401).send('Invalid signature');
}
// Handle the incoming email
if (event.type === 'message.received') {
const message = event.data.message;
console.log(`New email from: ${message.from}`);
console.log(`Subject: ${message.subject}`);
console.log(`Body: ${message.body.text}`);
// Process with your AI agent
const aiResponse = await yourAI.generateResponse(message);
// Send reply
await client.inbox.message.send({
inboxId: message.inboxId,
to: message.from,
subject: `Re: ${message.subject}`,
body: aiResponse,
inReplyTo: message.id
});
}
res.status(200).send('OK');
});
Query your agent's email history:
// Find all emails from a specific sender
const messages = await client.inbox.message.search({
inboxId: inbox.id,
from: 'customer@example.com',
limit: 50
});
// Search by content
const results = await client.inbox.message.search({
inboxId: inbox.id,
query: 'invoice payment',
dateRange: {
start: '2024-01-01',
end: '2024-01-31'
}
});
// Find all messages in a thread
const thread = await client.threads.get(threadId);
console.log(`Thread has ${thread.messages.length} messages`);
Attachments are automatically decoded and accessible:
app.post('/webhooks/email', async (req, res) => {
const { message } = req.body.data;
if (message.attachments && message.attachments.length > 0) {
for (const attachment of message.attachments) {
console.log(`Attachment: ${attachment.filename}`);
console.log(`Type: ${attachment.contentType}`);
console.log(`Size: ${attachment.size} bytes`);
console.log(`URL: ${attachment.url}`);
// Download and process
const response = await fetch(attachment.url);
const data = await response.arrayBuffer();
// Process with your AI (e.g., OCR, image analysis)
await processAttachment(data, attachment.contentType);
}
}
res.status(200).send('OK');
});
Use your own domain for professional emails:
// Add a domain
const domain = await client.domains.create({
domain: 'mail.yourcompany.com'
});
// Get DNS records to configure
console.log('Add these DNS records:');
console.log(domain.dnsRecords);
// Verify domain
await client.domains.verify(domain.id);
// Create inbox with custom domain
const inbox = await client.inboxes.create({
name: 'Sales Team',
domain: domain.id
});
// Now inbox.email will be: sales@mail.yourcompany.com
Tag emails with custom metadata for tracking:
const message = await client.inbox.message.send({
inboxId: inbox.id,
to: 'customer@example.com',
subject: 'Your Order Confirmation',
body: 'Thank you for your order!',
metadata: {
orderId: 'ORD-12345',
customerId: 'CUST-789',
campaign: 'summer-sale'
}
});
// Search by metadata
const orders = await client.inbox.message.search({
inboxId: inbox.id,
metadata: { campaign: 'summer-sale' }
});
Sendook sends various webhook events:
| Event Type | Description |
|---|---|
message.received | New email arrived in inbox |
message.sent | Email successfully sent |
message.delivered | Email delivered to recipient |
message.bounced | Email bounced |
message.opened | Recipient opened email (if tracking enabled) |
message.clicked | Recipient clicked link (if tracking enabled) |
Handle duplicate webhook deliveries gracefully:
const processedMessages = new Set();
app.post('/webhooks/email', async (req, res) => {
const messageId = req.body.data.message.id;
// Check if already processed
if (processedMessages.has(messageId)) {
return res.status(200).send('Already processed');
}
// Process message
await processMessage(req.body.data.message);
// Mark as processed
processedMessages.add(messageId);
res.status(200).send('OK');
});
Be mindful of API rate limits:
import pLimit from 'p-limit';
const limit = pLimit(10); // Max 10 concurrent requests
const messages = customers.map(customer =>
limit(() => client.inbox.message.send({
inboxId: inbox.id,
to: customer.email,
subject: 'Update from our team',
body: `Hi ${customer.name}...`
}))
);
await Promise.all(messages);
Always handle API errors gracefully:
try {
await client.inbox.message.send({
inboxId: inbox.id,
to: 'invalid-email',
subject: 'Test',
body: 'Test'
});
} catch (error) {
if (error.code === 'INVALID_EMAIL') {
console.error('Email address is invalid');
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
console.error('Rate limit exceeded, retry later');
} else {
console.error('Unexpected error:', error);
}
}