Developer docs
Get an API keyIotaBot Email API
Send from your own domain, reply in thread, and receive every email as JSON.
Send and receive email with Node.js
Send and receive from Node.js — built-in fetch and Express.
- Create an API key (a test key never sends) and a webhook endpoint in Developers.
- Set
IOTABOT_API_KEYandIOTABOT_WEBHOOK_SECRET(the endpoint'swhsec_…secret) in your environment. - Run the program below. Point the endpoint at
/iotabot/webhookon 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);
