Usage Guide

Master Sendook's core workflows for AI agent email communication.

This guide covers the essential workflows for integrating Sendook into your AI agent.

Core Concepts

Organizations

Every account belongs to an Organization. Organizations contain:

  • Inboxes
  • API Keys
  • Team members
  • Custom domains
  • Billing settings

Inboxes

An Inbox is a mailbox your AI agent can send and receive from. Each inbox has:

  • A unique email address (e.g., inbox-123@sendook.com)
  • Independent message storage
  • Configurable webhooks
  • Optional custom domain

Messages

Messages are emails sent or received by an inbox. They include:

  • Sender and recipients
  • Subject and body (HTML and plain text)
  • Attachments (decoded and accessible via URL)
  • Thread ID for conversation tracking
  • Metadata for custom attributes

Common Workflows

1. Create an Inbox

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
Pro tip: Create separate inboxes for different purposes (support, sales, notifications) to keep messages organized.

2. Send an Email

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'
    }
  ]
});

3. Reply to a Thread

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
});

4. Receive Emails via Webhooks

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');
});
Important: Always verify webhook signatures to ensure requests are from Sendook.

5. Search Messages

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`);

6. Handle Attachments

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');
});

Advanced Features

Custom Domains

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

Email Metadata

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' }
});

Webhook Events

Sendook sends various webhook events:

Event TypeDescription
message.receivedNew email arrived in inbox
message.sentEmail successfully sent
message.deliveredEmail delivered to recipient
message.bouncedEmail bounced
message.openedRecipient opened email (if tracking enabled)
message.clickedRecipient clicked link (if tracking enabled)

Best Practices

1. Idempotent Processing

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');
});

2. Rate Limiting

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);

3. Error Handling

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);
  }
}