SKILL.md
Salla Communication App
A Communication App is an App Function that takes over message delivery for SMS, Email, or WhatsApp. When a store event fires (order status change, OTP request, abandoned cart, …), Salla calls your function with the composed message. Your function reads provider credentials from context.settings, calls the provider (Twilio / SendGrid / 360dialog / Unifonic / any), and returns the result via the Resp utility. You own routing and delivery; Salla owns the trigger. (Overview)
Prerequisite: a working grasp of App Functions ([salla-app-functions](../salla-app-functions/SKILL.md)) — the runtime, Resp, and the sandbox limits are not duplicated here. (Get Started)
Step 1 — Create the app
Follow [salla-app-builder](../salla-app-builder/SKILL.md) with type: "communication". Selecting the Communication App category is what unlocks the Supported Features section (Step 2) — without it the channel options never appear. (Channels Config) Deltas from a General App:
- No
subcategoryid— communication apps don't use one (onlyappandshippingtypes
do). They still need a maincategoryid at publish — that's the shared "App Theme"/"App Impact" list (sallareference action=categories → maincategories), not a communication-specific tree.
- 0 default webhooks — nothing is subscribed for you (shipping apps get default shipment events; you get none).
is_embeddeddefaults totrue.
Gate: "App created with type: "communication", and the Supported Features section is now visible?"
Step 2 — Declare supported channels (publish-blocker)
Supported Features declare which channels your app handles. Once a merchant installs your app and sets it as the active handler for a channel, Salla stops using its default delivery layer and routes every matching message to your App Function — your app takes full ownership of that message type. Before publishing you MUST declare at least one channel: (Channels Config)
- MCP:
sallasettings action=setfeatureswith any ofsms_local,
smsinternational, emailall, whatsapp.
- Read currently-set features:
sallasettings action=listfeatures. (Returns only the features already configured, not the full types schema. Valid channel values —smslocal,smsinternational,email_all,whatsapp— come from the tool schema, not the API response. The tool wraps the supported-features endpoints — never call them directly.)
| Feature | set_features value |
Event | What you handle |
|---|---|---|---|
| Local SMS | sms_local |
communication.sms.send |
SMS to KSA numbers (+966…) |
| International SMS | sms_international |
communication.sms.send |
SMS outside KSA |
email_all |
communication.email.send |
All email | |
whatsapp |
communication.whatsapp.send |
All WhatsApp |
Publishing without features fails with 403 communicationappnothavefeatures (slug and channel values are surfaced by the salla_settings tool schema — verify current values via the Partners MCP or the Portal if they ever change).
SMS apps need a CITC certification to publish. A communication app that supports SMS
(smslocal/smsinternational) must upload its CITC certification on the account
verification form (https://portal.salla.partners/account) — a Saudi regulatory requirement.
App details exposerequirescitc(apppublish action=get/salla_apps action=get):
whentrue, the certificate is still needed. The partner verifies the account and uploads it
before the publish request; until then submission is blocked. Publication flow →
salla-publication-consistency.
Gate: "At least one channel declared via sallasettings action=setfeatures — and, for an SMS app, requires_citc reads false?"
Step 3 — Set up the provider
Pick a delivery provider and obtain its credentials before writing code. The doc walks Twilio as the worked case (Provider/Twilio): create an account, claim a phone number, and save the Account SID, Auth Token, and sender number (for WhatsApp, join the Twilio sandbox from your own verified number — in testing, messages only reach verified sandbox numbers). The same shape applies to any provider (SendGrid API key, Meta Graph token + phone-number id, etc.) — only the field names differ.
You enter these as App Settings so each merchant supplies their own; never hardcode them. The settings FORM definition (field schema, public:false for secrets) lives in [salla-app-settings](../salla-app-settings/SKILL.md); at runtime they arrive as context.settings.
Step 4 — Build the App Function
The three events are App Function triggers — per the hookable rule, implement them as App Functions ([salla-app-functions](../salla-app-functions/SKILL.md)) rather than webhooks: no server, and the merchant's provider credentials arrive in context.settings.
In the Portal function builder, click Select Action and pick the channel event you want to handle. One function per channel, or a single master function that branches on context.payload.event. The handler reads the payload + settings, calls the provider, and returns Resp.success() / Resp.error():
export default async (context: CommunicationEvent): Promise<Resp> => {
const { payload, settings } = context;
const { notifiable, content } = payload.data;
if (!settings.sms_api_key) {
return Resp.error()
.setMessage("Missing provider credentials.")
.setStatus(422);
}
try {
const res = await fetch(`${settings.sms_base_url}/send`, {
method: "POST",
signal: AbortSignal.timeout(10_000),
headers: {
Authorization: `Bearer ${settings.sms_api_key}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ to: notifiable[0], text: content }),
});
return res.ok ? Resp.success() : Resp.error().setStatus(res.status);
} catch (err: any) {
return Resp.error().setMessage("Delivery failed.").setStatus(500);
}
};
Every event delivers the same payload in context.payload.data (notifiable, type, content, entity, meta) plus credentials in context.settings. Local SMS and International SMS share the single communication.sms.send event — route by the notifiable[0] number prefix if you need provider-specific handling. Full event list, typed shape, real examples, and handling patterns: [references/communication-events.md](references/communication-events.md).
Only fall back to webhook subscriptions for these events if delivery must run on your own infrastructure ([salla-webhooks](../salla-webhooks/SKILL.md)).
This app sends real customer messages, so keep content, notifiable, and settings credentials out of logs and validate the provider endpoint before calling it — full secret & PII rules in [references/communication-events.md](references/communication-events.md).
Gate: "Each channel handled by an App Function that reads credentials from context.settings and returns Resp.success()/Resp.error() — no hardcoded keys, no custom server?"
Step 5 — Test
Two stages (Test & Go Live):
- Preview panel — in the function editor, Select Store (demo store), enter a
preview parameter (usually a customer id whose phone is your verified test number), then Save and Preview. The panel shows execution status, returned data, run time, console.log() output, and error traces. If context.settings is empty, you haven't filled the App Settings form on the demo store. Preview via [salla-app-functions-test](../salla-app-functions-test/SKILL.md).
- End-to-end from the dashboard — install on the demo store, set your app as the
active handler under Apps → Settings → Customize, then trigger a real message (e.g. change an order status to fire communication.*.send) and confirm it arrives via your provider. Full demo-store run: [salla-live-testing](../salla-live-testing/SKILL.md).
Gate: "A real communication.*.send event from the demo store delivered end-to-end through your provider?"
Step 6 — Go live
Standard publish flow via [salla-app-builder](../salla-app-builder/SKILL.md). Gate: Step 2 features are set and send handlers pass both test stages above. Edits stay in a sandbox until you publish; merchants who already installed the app receive the updated function automatically once published — no reinstall. (Test & Go Live)
Red Flags
| Tempting thought | Why it's wrong |
|---|---|
| "I'll declare the supported features later, after the code works." | Publishing with zero declared channels fails 403 communicationappnothavefeatures — declare ≥1 via sallasettings action=setfeatures BEFORE publish (Step 2). |
| "The SMS app is ready to submit once delivery works." | An SMS app (smslocal/smsinternational) stays blocked until the CITC certificate is uploaded on the account verification form — confirm requires_citc is false first (Step 2). |
| "I'll stand up a webhook server to receive the send events." | The send events are App Function triggers — implement them as App Functions (no server; credentials arrive in context.settings). Fall back to webhooks only if delivery must run on your own infra (Step 4). |
| "I'll store the merchant's provider keys in my own database." | Provider credentials are per-merchant App Settings read from context.settings at runtime — never hardcode them or store them outside salla_settings (Step 3). |
Resources
| Topic | Link |
|---|---|
| Overview | https://docs.salla.dev/2006115m0.md |
| Get Started | https://docs.salla.dev/2006118m0.md |
| App Channels Configuration | https://docs.salla.dev/2081234m0.md |
| Set Up Your Provider (Twilio) | https://docs.salla.dev/2081235m0.md |
| Build Your App Function | https://docs.salla.dev/2081248m0.md |
| Test & Go Live | https://docs.salla.dev/2081250m0.md |
| Event & Payload Reference | https://docs.salla.dev/2006119m0.md |
| Examples | https://docs.salla.dev/2006120m0.md |