anu

Hex.pmDocsLicense

Composable Elixir SDK for the WhatsApp Business API.

Anu is an open core platform for building on WhatsApp. This repo contains the Elixir SDK — the first official client. TypeScript, Python, and Go SDKs are coming.

Docs · Landing page · Cloud

Quick look

Anu.Message.new("5511999999999")
|> Anu.Message.text("Your order has shipped!")
|> Anu.Message.buttons([
  {"Track", :track_order},
  {"Cancel", :cancel}
])
|> Anu.deliver()

Installation

Requires Elixir 1.19+ and OTP 27+.

Add anu to your dependencies in mix.exs:

def deps do
  [
    {:anu, "~> 0.1"}
  ]
end

Configuration

Anu talks directly to Meta's Cloud API. Bring your own token:

# config/config.exs
config :anu,
  access_token: System.get_env("WHATSAPP_ACCESS_TOKEN"),
  phone_number_id: System.get_env("WHATSAPP_PHONE_NUMBER_ID"),
  verify_token: System.get_env("WHATSAPP_VERIFY_TOKEN")

Finch pool

Anu uses Finch for HTTP. You can configure the connection pool to tune concurrency for your workload:

# config/config.exs
config :anu, :finch_pool,
  size: 50,
  count: 4

Or start a named Finch instance and pass it in:

# In your application supervisor
children = [
  {Finch, name: MyApp.Finch, pools: %{
    "https://graph.facebook.com" => [size: 100, count: 8]
  }}
]

# config/config.exs
config :anu, finch: MyApp.Finch

Usage

Composing messages

Messages are built by piping through composable functions — no macros, no DSLs:

# Simple text
Anu.Message.new(to)
|> Anu.Message.text("Hello!")
|> Anu.deliver()

# Rich interactive message
Anu.Message.new(to)
|> Anu.Message.header_image("https://example.com/menu.jpg")
|> Anu.Message.body("Check out our new menu")
|> Anu.Message.footer("Open daily 8am–10pm")
|> Anu.Message.buttons([
  {"Order now", :order},
  {"View hours", :hours}
])
|> Anu.deliver()

# List message with sections
Anu.Message.new(to)
|> Anu.Message.body("What can I help you with?")
|> Anu.Message.button_text("Choose an option")
|> Anu.Message.sections([
  Anu.Section.new("Orders", [
    Anu.Row.new("order_status", "Order status"),
    Anu.Row.new("order_cancel", "Cancel order")
  ]),
  Anu.Section.new("Account", [
    Anu.Row.new("account_info", "Account info"),
    Anu.Row.new("account_help", "Get help")
  ])
])
|> Anu.deliver()

# Location
Anu.Message.new(to)
|> Anu.Message.location(-23.5505, -46.6333, name: "São Paulo", address: "SP, Brazil")
|> Anu.deliver()

# React to a message
Anu.Message.new(to)
|> Anu.Message.react("👍", message_id: original_msg_id)
|> Anu.deliver()

Templates

Anu.Message.new(to)
|> Anu.Message.template("order_confirmation", "pt_BR", [
  Anu.Template.body_param("João"),
  Anu.Template.body_param("#12345")
])
|> Anu.deliver()

Webhook handling

Drop the plug into your Phoenix router:

# lib/my_app_web/router.ex
forward "/webhooks/whatsapp", Anu.Webhook.Plug,
  handler: MyApp.WhatsAppHandler

Implement the handler behaviour:

defmodule MyApp.WhatsAppHandler do
  @behaviour Anu.Webhook.Handler

  @impl true
  def handle_event(:message_received, %Anu.Event.Message{} = msg) do
    msg.from
    |> Anu.Message.new()
    |> Anu.Message.react("👍", message_id: msg.id)
    |> Anu.deliver()
  end

  @impl true
  def handle_event(:message_status, %Anu.Event.Status{} = status) do
    # status.id, status.status (:sent, :delivered, :read, :failed)
    :ok
  end

  @impl true
  def handle_event(_event, _payload), do: :ok
end

Adapters

Like Swoosh, Anu supports multiple adapters:

# config/config.exs

# Production — Meta Cloud API (default)
config :anu, adapter: Anu.Adapters.Meta

# Development — logs messages to console
config :anu, adapter: Anu.Adapters.Local

# Test — stores messages in-process
config :anu, adapter: Anu.Adapters.Test

In tests:

import Anu.TestAssertions

test "sends order confirmation" do
  MyApp.send_confirmation(order)

  assert_message_sent(to: order.customer_phone, body: "Your order has shipped!")
end

Anu Cloud

The open source SDK handles messaging and webhooks. Anu Cloud adds AI primitives and a workflow engine as a hosted API.

# Add the cloud client
{:anu_cloud, "~> 0.1"}

AI primitives

# Classify intent
{:ok, %{intent: :order_status, confidence: 0.95}} =
  Anu.AI.classify(msg, intents: [:order_status, :complaint, :general])

# Generate contextual reply
Anu.Message.new(msg.from)
|> Anu.AI.reply(context: order_data, tone: :friendly)
|> Anu.deliver()

# Extract structured data
{:ok, %{name: "João", order_id: "12345"}} =
  Anu.AI.extract(msg, schema: %{name: :string, order_id: :string})

# Summarize conversation
{:ok, summary} = Anu.AI.summarize(conversation_id)

Workflows

Anu.Workflow.new("support")
|> Anu.Workflow.on_message(match: :any)
|> Anu.Workflow.step(:classify, &Anu.AI.classify(&1, intents: [:order, :billing, :other]))
|> Anu.Workflow.branch(%{
  order:   &Anu.AI.reply(&1, context: :orders_db),
  billing: &Anu.Workflow.handoff(&1, to: :human_agent),
  other:   &Anu.AI.reply(&1, fallback: true)
})
|> Anu.Workflow.deploy()

Zero markup pricing

Anu does not mark up Meta's messaging fees. You pay Meta's per-message cost directly. The cloud service charges only for AI usage and the platform subscription.

Other SDKs

This is the Elixir SDK. Other official SDKs are in development:

SDK Status Repo
Elixir Available zoedsoupe/anu
TypeScript Coming soon
Python Coming soon
Go Coming soon

You can also use the REST API directly from any language.

Contributing

Contributions are welcome! Please read CONTRIBUTING.md before submitting a PR.

git clone https://github.com/zeeetech/anu_ex.git
cd anu
mix deps.get
mix test

Built with 💜 by @zoedsoupe