npm package: @ariaflowagents/messaging-meta
Description: Meta platform clients (WhatsApp, Messenger, Instagram) for AriaFlow messaging
Latest version: 0.8.1

# @ariaflowagents/messaging-meta

Meta platform clients (WhatsApp, Messenger, Instagram) for AriaFlow messaging. Each client implements the `PlatformClient` interface from `@ariaflowagents/messaging`.

## Install

```bash
npm install @ariaflowagents/messaging-meta @ariaflowagents/messaging
```

## WhatsApp

```ts
import { createWhatsAppClient } from '@ariaflowagents/messaging-meta/whatsapp';

const whatsapp = createWhatsAppClient({
  accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
  appSecret: process.env.META_APP_SECRET!,
  phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
  verifyToken: process.env.WHATSAPP_VERIFY_TOKEN!,
});

// Listen for messages
whatsapp.onMessage(async (msg) => {
  await whatsapp.markAsRead(msg.id);
  await whatsapp.sendText(msg.from.phone!, `You said: ${msg.text}`);
});

// Mount webhook (Hono)
app.route('/whatsapp', whatsapp.webhookRouter());
```

### Templates and Interactive Messages

```ts
import { buildTemplateSendPayload } from '@ariaflowagents/messaging-meta/whatsapp';

// Template
await whatsapp.sendTemplate('+1234567890', buildTemplateSendPayload({
  name: 'order_confirmation',
  language: { code: 'en_US' },
  components: [{ type: 'body', parameters: [{ type: 'text', text: 'ORD-1234' }] }],
}));

// Buttons
await whatsapp.sendInteractiveButtons('+1234567890', {
  body: 'How can we help?',
  buttons: [{ id: 'sales', title: 'Sales' }, { id: 'support', title: 'Support' }],
});

// List
await whatsapp.sendListMessage('+1234567890', {
  body: 'Select a department',
  button: 'View Options',
  sections: [{ title: 'Departments', rows: [{ id: 'eng', title: 'Engineering' }] }],
});
```

### WhatsApp-Specific Methods

| Method | Description |
|---|---|
| `sendText(to, text)` | Send text (auto-splits at 4096 chars) |
| `sendMedia(to, media)` | Send image, video, audio, document, sticker |
| `sendTemplate(to, template)` | Send approved template message |
| `sendTextOrTemplate(to, opts)` | Send text if window open, template if closed |
| `sendInteractiveButtons(to, msg)` | Send button message (max 3) |
| `sendListMessage(to, list)` | Send list picker |
| `sendCTAButton(to, cta)` | Send call-to-action URL button |
| `sendInteractiveFlow(to, flow)` | Launch a WhatsApp Flow |
| `sendReaction(to, messageId, emoji)` | React to a message |
| `sendLocation(to, location)` | Send a location pin |
| `sendContacts(to, contacts)` | Send contact cards |
| `uploadMedia(file, options)` | Upload media, get `MediaHandle` |
| `downloadMedia(mediaId)` | Download media by ID |
| `markAsRead(messageId)` | Mark message as read |

## Messenger

```ts
import { createMessengerClient } from '@ariaflowagents/messaging-meta/messenger';

const messenger = createMessengerClient({
  pageAccessToken: process.env.MESSENGER_PAGE_ACCESS_TOKEN!,
  appSecret: process.env.META_APP_SECRET!,
  pageId: process.env.MESSENGER_PAGE_ID!,
  verifyToken: process.env.MESSENGER_VERIFY_TOKEN!,
});

messenger.onMessage(async (msg) => {
  await messenger.sendText(msg.from.id, `Echo: ${msg.text}`);
});
```

### Templates and Quick Replies

```ts
// Button template
await messenger.sendButtonTemplate(psid, {
  text: 'What would you like to do?',
  buttons: [{ type: 'postback', title: 'Track Order', payload: 'TRACK' }],
});

// Generic template (carousel)
await messenger.sendGenericTemplate(psid, {
  elements: [{ title: 'Product A', subtitle: '$29.99', image_url: 'https://example.com/a.jpg' }],
});

// Quick replies
await messenger.sendQuickReplies(psid, 'Choose one:', [
  { content_type: 'text', title: 'Yes', payload: 'YES' },
  { content_type: 'text', title: 'No', payload: 'NO' },
]);
```

### Messenger-Specific Methods

| Method | Description |
|---|---|
| `sendText(to, text)` | Send text (max 2000 chars) |
| `sendMedia(to, media)` | Send image, video, audio, or file |
| `sendButtonTemplate(to, template)` | Send button template (max 3 buttons) |
| `sendGenericTemplate(to, template)` | Send carousel (max 10 elements) |
| `sendQuickReplies(to, text, replies)` | Send quick reply buttons |
| `sendSenderAction(to, action)` | Send typing_on, typing_off, mark_seen |
| `getUserProfile(psid)` | Fetch user's name and profile picture |
| `uploadMedia(file, options)` | Upload media for reuse |
| `downloadMedia(mediaUrl)` | Download media by URL |

## Instagram

```ts
import { createInstagramClient } from '@ariaflowagents/messaging-meta/instagram';

const instagram = createInstagramClient({
  accessToken: process.env.INSTAGRAM_ACCESS_TOKEN!,
  appSecret: process.env.META_APP_SECRET!,
  igId: process.env.INSTAGRAM_ACCOUNT_ID!,
  verifyToken: process.env.INSTAGRAM_VERIFY_TOKEN!,
});

instagram.onMessage(async (msg) => {
  await instagram.sendText(msg.from.id, `Thanks for your message!`);
});
```

### Ice Breakers

```ts
// Set conversation starters
await instagram.iceBreakers.set([
  { question: 'What are your hours?', payload: 'HOURS' },
  { question: 'Track my order', payload: 'TRACK' },
]);

// Get current ice breakers
const breakers = await instagram.iceBreakers.get();

// Delete all
await instagram.iceBreakers.delete();
```

### Instagram-Specific Methods

| Method | Description |
|---|---|
| `sendText(to, text)` | Send text (auto-splits at 1000 bytes) |
| `sendMedia(to, media)` | Send image attachment only |
| `sendQuickReplies(to, text, replies)` | Send quick reply buttons (max 13) |
| `sendGenericTemplate(to, template)` | Send carousel template |
| `sendButtonTemplate(to, template)` | Send button template |
| `sendPrivateReply(options)` | Reply privately to a comment |
| `sendTextWithTag(to, text, tag)` | Send with message tag (e.g. HUMAN_AGENT) |
| `iceBreakers.set(breakers)` | Set conversation starters |
| `iceBreakers.get()` | Get current ice breakers |
| `iceBreakers.delete()` | Remove all ice breakers |

## Standalone Webhook Utilities

For server-side webhook handling without the full client:

```ts
import { verifySignature, normalizeWebhook } from '@ariaflowagents/messaging-meta/server';

app.post('/webhook', async (c) => {
  const rawBody = await c.req.text();
  const sig = c.req.header('x-hub-signature-256') ?? '';

  if (!verifySignature({ appSecret, rawBody, signatureHeader: sig })) {
    return c.text('Invalid signature', 403);
  }

  const events = normalizeWebhook(JSON.parse(rawBody));
  // events.messages, events.statuses, events.reactions
});
```

## Platform Comparison

| Feature | WhatsApp | Messenger | Instagram |
|---|:---:|:---:|:---:|
| Text limit | 4096 chars | 2000 chars | 1000 bytes |
| Media types | all | all | image only |
| Buttons / List / Carousel | 3 / yes / no | 3 / no / 10 | 3 / no / 10 |
| Quick replies | no | 13 | 13 |
| Templates | yes | yes | yes |
| Typing + reactions | yes | yes | yes |
| Message window | 24h | none | 7d (tag) |
| Unique features | Flows, CTA, lists | Personas, profiles | Ice breakers, private reply |

## AriaFlow Integration

Use with `@ariaflowagents/messaging` to connect clients to the AriaFlow runtime:

```ts
import { createMessagingRouter } from '@ariaflowagents/messaging';

const router = createMessagingRouter({ runtime, platforms: { whatsapp, messenger, instagram } });
app.route('/messaging', router);
// Webhooks: /messaging/{whatsapp,messenger,instagram}/webhook
```

## Subpath Exports

| Import path | Contents |
|---|---|
| `@ariaflowagents/messaging-meta` | GraphAPIClient, errors, webhook utilities |
| `@ariaflowagents/messaging-meta/whatsapp` | WhatsAppClient, templates, flows, format converter |
| `@ariaflowagents/messaging-meta/messenger` | MessengerClient, format converter |
| `@ariaflowagents/messaging-meta/instagram` | InstagramClient, ice breakers, format converter |
| `@ariaflowagents/messaging-meta/server` | `verifySignature` + `normalizeWebhook` only |

## Related

- [`@ariaflowagents/messaging`](../ariaflow-messaging) -- Core interfaces and runtime adapter
- [`@ariaflowagents/core`](../ariaflow-core) -- Runtime, agents, flows