Developer docs

IotaBot Email API

Send from your own domain, reply in thread, and receive every email as JSON.

Get an API key

Send and receive email with Node.js

Send and receive from Node.js — built-in fetch and Express.

  1. Create an API key (a test key never sends) and a webhook endpoint in Developers.
  2. Set IOTABOT_API_KEY and IOTABOT_WEBHOOK_SECRET (the endpoint's whsec_… secret) in your environment.
  3. Run the program below. Point the endpoint at /iotabot/webhook on your server.
app.js
// npm install express      (Node.js 18+: fetch is built in)
const crypto = require("crypto");
const express = require("express");

const API = "https://api.iotabot.com/v1";
const KEY = process.env.IOTABOT_API_KEY;
const SECRET = process.env.IOTABOT_WEBHOOK_SECRET;   // the endpoint's signing secret (whsec_…)

async function send(body) {
  // One Idempotency-Key per message: reuse it if you retry, so it is never sent twice.
  const res = await fetch(`${API}/messages`, {
    method: "POST",
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
    body: JSON.stringify(body),
  });
  const { data, error } = await res.json();
  if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
  return data;
}

// 1. Start a new email thread from one of your verified addresses (GET /v1/email/senders).
send({
  channel: "email",
  to: "priya@example.com",
  from: "support@acme.com",
  subject: "Your order #1042",
  html: "<p>Hi Priya, your order shipped yesterday.</p>",
}).then(
  m => console.log("queued", m.id, "in conversation", m.conversation_id),
  err => console.error("send refused:", err.message),   // e.g. template_not_approved — use your own
);

// 2. Receive inbound email as JSON and reply in the same thread.
function verified(raw, header) {
  const parts = header.split(",").map(p => p.split("="));
  const t = Number(parts.find(([k]) => k === "t")?.[1]);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;   // older than 5 minutes
  const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${raw}`).digest();
  return parts.filter(([k]) => k === "v1").some(([, v]) => {
    const got = Buffer.from(v, "hex");
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}

const app = express();
app.post("/iotabot/webhook", express.raw({ type: "application/json" }), async (req, res) => {
  const event = JSON.parse(req.body);
  if (event.type === "endpoint.verification") {                  // sent once, when you add the endpoint
    return res.json({ challenge: event.data.challenge });
  }
  if (!verified(req.body, req.get("IotaBot-Signature") ?? "")) return res.sendStatus(401);
  res.sendStatus(200);                                           // answer fast, then do the work
  if (event.type === "message.received") {
    const { conversation, message } = event.data;
    console.log(message.email.from, "wrote:", message.email.subject);
    await send({ conversation_id: conversation.id, text: "Thanks — we are on it!" });   // threads onto their email
  }
});
app.listen(3000);