Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

anu: TypeScript SDK

npm License

Composable TypeScript SDK for the WhatsApp Business Cloud API.

Docs · Landing page

Installation

Requires Node 18+ (or any runtime with fetch and Request/Response).

npm install anu-whatsapp

Quick start

Delivery 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.

Multiple numbers / WABAs

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

Composing messages

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"),
  ]),
);

Webhook handling

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

AI helpers (client.ai)

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.

Adapters

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

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages