Chat SDK

Chat SDK

A pattern for building conversational, two-way email on MailBlastr — send with the SDK, receive via webhooks, thread with email headers.

You can turn email into a two-way communication channel for an agent or app: send with the official `mailblastr` SDK (or POST /emails), receive inbound mail through webhooks, and thread conversations using standard email headers. This page describes that pattern and shows how to package it as a Vercel Chat SDK adapter so reply chains map cleanly onto Chat SDK threads.

MailBlastr does not publish a prebuilt Chat SDK adapter package. The adapter below is a thin wrapper *you* write over the real primitives (the SDK + inbound webhooks). It is roughly 100 lines; the sections that follow give you the building blocks.

Prerequisites

1. Send with the SDK

Install the official Node SDK. Every reply you post is just an emails.send call from a verified address.

npm install mailblastr
import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr(process.env.MAILBLASTR_API_KEY!);

async function reply(to: string, subject: string, text: string, headers?: Record<string, string>) {
  const { data, error } = await mb.emails.send({
    from: 'My Bot <bot@yourdomain.com>',
    to: [to],
    subject,
    text,
    headers, // set In-Reply-To / References here to keep the thread intact
  });
  if (error) throw new Error(`${error.name}: ${error.message}`);
  return data;
}

2. Receive via webhooks

Point a MailBlastr webhook at your server and subscribe to the email.received event. Verify the signature against your signing secret, then fetch the full message — including headers and body — from the Receiving API.

// POST /webhook
async function handler(req: Request) {
  const payload = await req.text();

  // Verify the signature against MAILBLASTR_WEBHOOK_SECRET before trusting input.
  if (!verifyMailblastrSignature(payload, req.headers, process.env.MAILBLASTR_WEBHOOK_SECRET!)) {
    return new Response('invalid signature', { status: 400 });
  }

  const event = JSON.parse(payload);
  if (event.type === 'email.received') {
    // Fetch the full inbound message (headers, text, html, attachments).
    const { data: email } = await mb.emails.receiving.get(event.data.email_id);
    await onInbound(email);
  }
  return new Response('ok', { status: 200 });
}

See Webhook signatures for the verification details and Receiving for the inbound message shape.

3. Thread with email headers

Email threading is carried entirely by standard headers — there is no special API. When you reply, set In-Reply-To to the inbound message's Message-ID and append it to References. Group inbound messages into a conversation by following the same chain.

async function onInbound(email) {
  const messageId = email.headers['message-id'];
  const references = [email.headers['references'], messageId].filter(Boolean).join(' ');

  await reply(email.from, `Re: ${email.subject}`, `Got your email: ${email.text}`, {
    'In-Reply-To': messageId,
    'References': references,
  });
}
Email is immutable: there is no edit, delete, reaction, or typing indicator. A "conversation" is just a chain of sent and received messages linked by In-Reply-To/References.

Packaging it as a Chat SDK adapter

If you use the Vercel Chat SDK, you can wrap the three steps above in a custom adapter so reply chains surface as Chat SDK threads. The adapter's post maps to mb.emails.send, its webhook handler maps to step 2, and thread resolution uses the In-Reply-To/References headers from step 3. A minimal shape:

import { MailBlastr } from 'mailblastr';

// A custom adapter you author — not a published package.
function createMailblastrAdapter(opts: { fromAddress: string; fromName?: string }) {
  const mb = new MailBlastr(process.env.MAILBLASTR_API_KEY!);
  return {
    name: 'mailblastr',
    async post(thread, text) {
      await mb.emails.send({
        from: opts.fromName ? `${opts.fromName} <${opts.fromAddress}>` : opts.fromAddress,
        to: [thread.recipient],
        subject: thread.subject,
        text,
        headers: thread.threadHeaders, // In-Reply-To / References
      });
    },
    // verify signature -> fetch via mb.emails.receiving.get -> resolve thread by headers
    async handleWebhook(request) { /* step 2 + step 3 */ },
  };
}
Keep the adapter name "mailblastr" consistent across your Chat config and your thread routing so inbound emails resolve to the right thread.

Going further