# Examples

These examples demonstrate how to process different communication channels. Each handler receives `settings` which are pre-filled based on the **App Settings** you defined in the Partner Portal.

---

### Channel Handlers

<Accordion title="SMS Handler" icon="material-two-tone-sms" defaultOpen={open}>
Handles `communication.sms.send`. This example demonstrates using a generic SMS gateway with a 10-second timeout.

**Key logic:**
* Validates that the merchant has configured their API key in the Partner Portal.
* Uses the first recipient from the `notifiable` array for SMS delivery.

```typescript
export default async (context: CommunicationEvent): Promise<Resp> => {
  const { payload, settings } = context;
  const { notifiable, content } = payload.data;

  // 1. Validate Partner Portal Settings
  if (!settings.sms_api_key || !settings.sms_sender_id) {
    return Resp.error()
      .setMessage("Missing SMS settings in Partner Portal.")
      .setStatus(422)
      .setData({});
  }

  try {
    const response = await fetch(`${settings.sms_base_url}/send`, {
      method: "POST",
      signal: AbortSignal.timeout(10_000), // 10s timeout
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${settings.sms_api_key}`,
      },
      body: JSON.stringify({
        from: settings.sms_sender_id,
        to:   notifiable[0],
        text: content,
      }),
    });

    if (!response.ok) {
      const raw = await response.json().catch(() => ({}));
      return Resp.error()
        .setMessage("Provider rejected request.")
        .setStatus(response.status)
        .setData({ raw });
    }

    const result = await response.json();
    return Resp.success()
      .setData({ message_id: result.id })
      .setMessage("SMS delivered.");

  } catch (err: any) {
    return Resp.error()
      .setMessage(err.name === "TimeoutError" ? "Provider timed out." : err.message)
      .setStatus(err.name === "TimeoutError" ? 503 : 500)
      .setData({});
  }
};
```
</Accordion>

<Accordion title="Email Handler (SendGrid)" icon="material-two-tone-email" defaultOpen={false}>
Demonstrates delivering communication.email.send events via the SendGrid API.

:::info
Email events support multiple recipients. This handler maps the notifiable array to the SendGrid personalizations object.
:::


```    
export default async (context: CommunicationEvent): Promise<Resp> => {
  const { payload, settings } = context;
  const { notifiable, content } = payload.data;

  if (!settings.email_api_key) {
    return Resp.error().setMessage("Missing Email API key.").setStatus(422).setData({});
  }

  try {
    const response = await fetch("[https://api.sendgrid.com/v3/mail/send](https://api.sendgrid.com/v3/mail/send)", {
      method: "POST",
      signal: AbortSignal.timeout(10_000),
      headers: {
        Authorization: `Bearer ${settings.email_api_key}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        personalizations: [{ to: notifiable.map((r) => ({ email: r })) }],
        from:    { email: settings.email_from_address },
        subject: "Order Update",
        content: [{ type: "text/html", value: content }],
      }),
    });

    if (!response.ok) {
      return Resp.error().setMessage("Email provider rejected request.").setStatus(response.status).setData({});
    }

    return Resp.success().setData({ message_id: response.headers.get("X-Message-Id") });

  } catch (err: any) {
    return Resp.error().setMessage(err.message).setStatus(500).setData({});
  }
};
```
</Accordion>

<Accordion title="WhatsApp Handler (Meta)" icon="material-two-tone-chat" defaultOpen={false}>
A standard WhatsApp handler utilizing the Meta Graph API for communication.whatsapp.send.

```
export default async (context: CommunicationEvent): Promise<Resp> => {
  const { payload, settings } = context;
  const { notifiable, content } = payload.data;

  if (!settings.whatsapp_api_key || !settings.whatsapp_phone_number_id) {
    return Resp.error().setMessage("Missing WhatsApp credentials.").setStatus(422).setData({});
  }

  try {
    const response = await fetch(`https://graph.facebook.com/v19.0/${settings.whatsapp_phone_number_id}/messages`, {
      method: "POST",
      signal: AbortSignal.timeout(10_000),
      headers: {
        Authorization: `Bearer ${settings.whatsapp_api_key}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        messaging_product: "whatsapp",
        to:   notifiable[0],
        type: "text",
        text: { body: content },
      }),
    });

    const result = await response.json();
    return response.ok 
      ? Resp.success().setData({ message_id: result.messages?.[0]?.id })
      : Resp.error().setStatus(response.status).setData({ result });

  } catch (err: any) {
    return Resp.error().setMessage(err.message).setStatus(500).setData({});
  }
};
```
</Accordion>

### Logic Patterns
<Accordion title="Handling High-Priority OTPs" icon="material-two-tone-speed" defaultOpen={open}>
OTP messages require high reliability and low latency. It is recommended to use shorter timeouts (e.g., 5 seconds) to fail fast and allow Salla to potentially retry or fall back.

```
export default async (context: CommunicationEvent): Promise<Resp> => {
  const { payload } = context;
  const { notifiable, content, type } = payload.data;

  // Filter for OTP only
  if (type !== 'auth.otp.verification') {
     return Resp.success().setMessage("Skipping non-OTP message.");
  }

  try {
    const response = await fetch("[https://api.provider.com/otp](https://api.provider.com/otp)", {
      method: "POST",
      signal: AbortSignal.timeout(5_000), // Faster 5s timeout
      body: JSON.stringify({ to: notifiable[0], message: content })
    });

    return response.ok ? Resp.success() : Resp.error();
  } catch (err: any) {
    return Resp.error();
  }
};
```
</Accordion>

<Accordion title="Multi-channel Routing" icon="material-two-tone-alt_route" defaultOpen={false}>
If your app supports all channels (SMS, Email, WhatsApp) within a single function, you can route logic based on the event property.

```
export default async (context: CommunicationEvent): Promise<Resp> => {
  const { event } = context.payload;

  switch (event) {
    case "communication.sms.send":
      return handleSms(context);
    case "communication.email.send":
      return handleEmail(context);
    case "communication.whatsapp.send":
      return handleWhatsApp(context);
    default:
      return Resp.error().setMessage("Unsupported channel.");
  }
};
```
</Accordion>

## Troubleshooting Common Errors

When your App Function returns a `Resp.error()`, Salla logs the status code and message. Use the table below to identify and fix common implementation hurdles.

| Status Code | Error Message | Common Cause | Recommended Fix |
| :--- | :--- | :--- | :--- |
| **422** | `Missing settings...` | The merchant hasn't filled in the required API keys in the app settings. | Check if the merchant has completed the app setup in the Salla Dashboard. |
| **503** | `Provider timed out` | The third-party SMS/Email API took longer than your `AbortSignal.timeout`. | Increase the timeout slightly (max 20s) or check the provider's status page. |
| **401/403** | `Unauthorized` | The API key provided in `settings` is invalid or expired. | Ensure the merchant is using a valid, active API key from their provider. |
| **429** | `Too Many Requests` | You've hit the rate limit of your SMS or Email provider. | Implement retry logic or check provider throughput limits. |
| **500** | `Internal Server Error` | Unhandled exception in your code (e.g., null pointer on `entity`). | Verify safe access to optional fields like `meta` or `entity`. |


:::check[Pro Tip: Testing Payloads]
Before going live, **Test Feature** in the Partner Portal to send dummy payloads to your function. This ensures your mapping logic for `notifiable` and `content` is solid across different event types.
:::
