SMS
The SMS tool (ctx.SMS) is a built-in tool available inside any loop. It queues text messages with SMS Portal and returns the portal event id for that send.
What this tool is for
- Texting a member a link, a reminder, or a short result.
- Sending the same content to many numbers in one step (up to 500).
What this tool is not for
- Not delivery confirmation.
eventIdmeans SMS Portal queued the message. It does not mean the handset received it. - Not a conversation. Replies are not delivered back into the loop.
Credentials
Set both under the loop’s Variables and Secrets, or on the worker. Store the API secret as a secret. A complete loop account wins; otherwise the worker account is used. The platform reads them when the loop sends — loop code does not pass them to ctx.SMS.send.
| Name | What it is |
|---|---|
SMS_PORTAL_CLIENT_ID | SMS Portal client id (Basic auth username) |
SMS_PORTAL_API_SECRET | SMS Portal API secret (Basic auth password) |
Tool API
Must be called inside a step.do callback — ctx only exists there. See Step context.
type SmsSendInput = {
/** Single recipient, an MSISDN such as "27821234567". */
to?: string;
content?: string;
customerId?: string;
/** Batch. Use this or `to`/`content`, not both. At most 500. */
messages?: Array<{
destination: string;
content?: string;
customerId?: string;
}>;
options?: {
senderId?: string;
testMode?: boolean;
checkOptOuts?: boolean;
shortenUrls?: boolean;
allowContentTrimming?: boolean;
campaignName?: string;
costCentre?: string;
validityPeriod?: number;
startDeliveryUtc?: string;
endDeliveryUtc?: string;
};
};
type SmsTool = {
send(input: SmsSendInput): Promise<{ eventId: string }>;
};send throws when credentials are missing, the portal rejects the request, or it queues zero messages (an invalid number, an opt-out, or another fault). Defaults: content trimming on, opt-out checks on, URL shortening off. Pass options to override those, or testMode: true to exercise the call without sending.
Example
import { Workflow } from "./loop";
export class HealthLoop extends Workflow {
async run(event, step) {
const { eventId } = await step.do("Text the member", async ({ ctx }) => {
return ctx.SMS.send({
to: event.body.phone,
content: "Your scan results are ready.",
});
});
return { eventId };
}
}