Skip to content

WhatsApp adapter: support sending Message Templates (business-initiated messages) #585

@shkumbinhasani

Description

@shkumbinhasani

Problem Statement

The WhatsApp adapter (@chat-adapter/whatsapp) can only send messages inside the 24-hour customer service window. The WhatsApp Business Cloud API requires pre-approved Message Templates (type: "template") for any business-initiated conversation — i.e. messaging a user who hasn't written to you in the last 24 hours.

Today the adapter has no way to send a template message:

  • postMessage() / stream() only emit type: "text", type: "interactive", and type: "reaction" payloads.
  • openDM() constructs the thread ID, but its own JSDoc notes you "can only message users who have messaged you first (within the 24-hour window) or via approved template messages" — without offering a way to actually do the latter.
  • There is no raw-payload escape hatch, so the only workaround is calling the Graph API directly, bypassing the SDK entirely.

This makes common WhatsApp use cases impossible through the SDK: notifications, reminders, order updates, re-engagement — anything where the bot speaks first.

Related docs nit: the feature matrix on the WhatsApp adapter page lists "Card format: WhatsApp templates" and "Fields: Template variables", but the implementation renders cards as interactive messages, not templates — which is also what the page body says. The frontmatter labels probably should say "Interactive messages" until/unless real template support lands.

Proposed Solution

Add first-class template support to the WhatsApp adapter, e.g. a sendTemplate() method on the adapter (and ideally surfaced on Thread):

await thread.sendTemplate({
  name: "order_shipped",
  language: "en_US",
  components: [
    {
      type: "body",
      parameters: [
        { type: "text", text: "Shkumbin" },
        { type: "text", text: "#12345" },
      ],
    },
  ],
});

Under the hood this posts to the same /{phoneNumberId}/messages endpoint with:

{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "<userWaId>",
  "type": "template",
  "template": { "name": "...", "language": { "code": "..." }, "components": [...] }
}

Scope notes:

  • Template quick-reply button responses already flow back in via handleButtonResponse(), so the inbound half exists — this is purely about the outbound side.
  • Since templates are platform-specific by nature (sent by name + variable components, not free-form markdown), this probably belongs as an adapter-level method rather than something forced through the PostableMessage/mdast pipeline.
  • Header media (image/document/video components) and quick-reply/CTA button components would be nice to support from day one, since most approved templates use them.

Alternatives Considered

  • Calling the Graph API directly alongside the SDK — works, but you lose the SDK's thread model, and the reply lands in a thread the SDK didn't initiate cleanly.
  • A generic postRaw(threadId, payload) escape hatch on the adapter — more flexible (covers other unsupported types too), but less discoverable and unvalidated.
  • Other adapters have a similar concept (Messenger's message tags / Generic+Button templates), so a shared abstraction might emerge later, but WhatsApp-specific support seems like the right first step.

Priority

Important — without it, the SDK only covers reactive WhatsApp bots, not business-initiated messaging.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions