Composable TypeScript SDK for the WhatsApp Business Cloud API.
Requires Node 18+ (or any runtime with fetch and Request/Response).
npm install anu-whatsappDelivery always goes through an explicit client holding the credentials for one WhatsApp phone number:
import { createClient, Message } from "anu-whatsapp";
const anu = createClient({
accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
});
const result = await anu.deliver(
Message.new("5511999999999")
.text("Your order has shipped!")
.buttons([
{ title: "Track", id: "track_order" },
{ title: "Cancel", id: "cancel" },
]),
);deliver returns a Result<T, AnuError>: delivery failure is a value, not
an exception. When you'd rather throw, use deliverOrThrow.
Build one client per phone number (or per WABA, when you hold several access tokens) and deliver through the matching one:
const support = createClient({ accessToken: "EAAG...", phoneNumberId: "111" });
const sales = createClient({ accessToken: "EAAG...", phoneNumberId: "222" });
await support.deliver(Message.new(to).text("hi"));See createClient for all options (adapter, apiVersion, fetch,
webhook, cloud).
Messages are built with an immutable builder. Each method returns a fresh
Message:
// Rich interactive message
await anu.deliver(
Message.new(to)
.headerImage("https://example.com/menu.jpg")
.text("Check out our new menu")
.footer("Open daily 8am-10pm")
.buttons([
{ title: "Order now", id: "order" },
{ title: "View hours", id: "hours" },
]),
);
// List message with sections
import { section, row } from "anu-whatsapp";
await anu.deliver(
Message.new(to)
.text("What can I help you with?")
.buttonText("Choose an option")
.sections([
section("Orders", [
row("order_status", "Order status"),
row("order_cancel", "Cancel order"),
]),
]),
);
// Location
await anu.deliver(
Message.new(to).location(-23.5505, -46.6333, { name: "Sao Paulo", address: "SP, Brazil" }),
);
// React to a message
await anu.deliver(Message.new(to).react("👍", { messageId: originalMessageId }));
// Template
import { template } from "anu-whatsapp";
await anu.deliver(
Message.new(to).template("order_confirmation", "pt_BR", [
template.bodyParam("Joao"),
template.bodyParam("#12345"),
]),
);client.webhook(handler) returns a framework-agnostic
(Request) => Promise<Response>. It handles Meta's hub.challenge GET
handshake and verifies the HMAC signature on POSTs. Pass your webhook
credentials to createClient:
const anu = createClient({
accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
webhook: {
verifyToken: process.env.WHATSAPP_VERIFY_TOKEN!,
appSecret: process.env.WHATSAPP_APP_SECRET!,
},
});
const handleWebhook = anu.webhook(async (event) => {
if (event.type === "message") {
const msg = event.event;
await anu.deliver(Message.new(msg.from).text("Thanks!"));
}
});
export default { fetch: handleWebhook };Since it speaks plain fetch types, it drops into any runtime (Node, Bun,
Deno, Workers). For Express / Fastify / raw Node http, use the
toFetchRequest / sendFetchResponse shim from the adapters/node
subpath.
Inbound events carry phoneNumberId and displayPhoneNumber from the
payload metadata, so you can route them back to the right client:
const handleWebhook = anu.webhook(async (event) => {
if (event.type === "message") {
const msg = event.event;
await clientFor(msg.phoneNumberId).deliver(Message.new(msg.from).text("Thanks!"));
}
});Optional cloud-backed AI helpers (classify, extract, reply,
summarize) powered by anu-cloud. Put your
anu_sk_... key on the client:
const anu = createClient({
accessToken: "...",
phoneNumberId: "...",
cloud: { apiKey: process.env.ANU_API_KEY! },
});
const classified = await anu.ai.classify("my order is late", {
intents: ["support", "sales", "spam"],
});
const drafted = await anu.ai.reply("when does my package arrive?", { tone: "warm" });All client.ai methods return a Result with a decoded map on success, or
an AnuError / cloud_not_configured error on failure. See the
anu-cloud docs for pricing, quotas, and the
full reference.
Anu supports multiple adapters, chosen per client:
import { metaAdapter, localAdapter } from "anu-whatsapp";
// Production: Meta Cloud API (default)
createClient({ accessToken: "...", phoneNumberId: "..." });
// Development: logs messages to the console
createClient({ accessToken: "...", phoneNumberId: "...", adapter: localAdapter });In tests, the anu-whatsapp/testing subpath wires a client to an in-memory
adapter so tests never touch the network:
import { createTestClient, assertMessageSent } from "anu-whatsapp/testing";
const { client, sent } = createTestClient();
await sendOrderConfirmation(client, order);
assertMessageSent(sent, { to: order.customerPhone, body: "Your order has shipped!" });Built with 💜 by @zoedsoupe