Portal Community

Node Configuration

{
  "nodeType": "SendSMS",
  "name": "sendApprovalSms",
  "config": {
    "credentialId": 15,
    "provider": "twilio",
    "from": "+15551234567",
    "to": "$output.fetchEmployee.mobilePhone",
    "body": "Action required: Invoice $output.createInvoice.invoiceNumber needs your approval. Log in at $context.appUrl",
    "statusCallbackUrl": null,
    "retryCount": 2,
    "retryDelayMs": 3000
  }
}

Configuration Fields

FieldTypeRequiredDescription
credentialIdintYesTwilio Account SID + Auth Token pair, stored via ICredentialResolver
providertwilio | vonageYesSMS provider. Determines which adapter is resolved from DI.
fromstringYesSender phone number in E.164 format (+15551234567). Must be a purchased Twilio number.
tostring / exprYesRecipient phone number in E.164 format. Expressions allowed.
bodystring / exprYesSMS body text. Max 1600 characters (multi-segment). Expression syntax supported.
statusCallbackUrlstringNoTwilio delivery status webhook URL for delivery receipts.
retryCountintNoRetry attempts on transient error. Default: 2.

Twilio Credential Storage

Twilio requires two values: Account SID and Auth Token. These are stored as a JSON object in the credential store and retrieved atomically:

// Stored credential value (JSON string)
// { "accountSid": "ACxxxxx", "authToken": "token-value" }

var rawCredential = await _credentials.GetPasswordAsync(config.CredentialId, ct);
var twilioAuth = JsonSerializer.Deserialize<TwilioCredential>(rawCredential);

TwilioClient.Init(twilioAuth.AccountSid, twilioAuth.AuthToken);
var message = await MessageResource.CreateAsync(
    to: new PhoneNumber(config.To),
    from: new PhoneNumber(config.From),
    body: evaluatedBody
);

SMSChannelAdapter

public class SMSChannelAdapter : IMessagingChannelAdapter
{
    public string ChannelType => "SMS";

    public async Task<MessagingResult> SendAsync(
        MessagingMessage message,
        MessagingConfig config,
        CancellationToken ct)
    {
        var rawCredential = await _credentials.GetPasswordAsync(config.CredentialId, ct);
        var auth = JsonSerializer.Deserialize<TwilioCredential>(rawCredential)!;

        TwilioClient.Init(auth.AccountSid, auth.AuthToken);

        var msg = await MessageResource.CreateAsync(
            to: new PhoneNumber(message.To),
            from: new PhoneNumber(config.From),
            body: message.Body,
            statusCallback: config.StatusCallbackUrl != null
                ? new Uri(config.StatusCallbackUrl)
                : null
        );

        return new MessagingResult
        {
            MessageId = msg.Sid,
            Channel = "sms",
            Recipient = message.To,
            SentAt = DateTimeOffset.UtcNow,
            Status = msg.Status.ToString().ToLower()
        };
    }
}

Node Output

{
  "messageId": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "channel": "sms",
  "sentAt": "2026-05-25T10:00:00Z",
  "status": "queued",
  "recipient": "+447700900123"
}
Character limits: Standard GSM SMS messages are 160 characters. Unicode messages (emoji, non-Latin characters) are 70 characters per segment. Twilio automatically splits long messages into multiple segments, each billed separately. Keep body expressions concise or check length before sending.
E.164 format: Always store and pass phone numbers in E.164 format (+{country code}{number}). Use an expression to normalise if numbers from your data source may vary: "$output.fetchEmployee.mobilePhone.replace(/[^+\d]/g, '')"