From 0ac37338ab2b251b96cbbfd38f1ab9a7be71d89e Mon Sep 17 00:00:00 2001 From: Wabo Date: Tue, 19 May 2026 05:21:50 -0400 Subject: [PATCH] Add Twilio, VoIP.ms, and SignalWire SMS providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds three new selectable providers behind the existing Sms.send_message interface. Per-account credentials are namespaced in the encrypted sms_configs hash (twilio_*, voipms_*, signalwire_*) so existing BulkVS configs keep working unchanged. - lib/sms.rb: dispatch via per-provider classes and delegate the "is this configured" check to each provider, replacing the BulkVS-only hardcoded gate in enabled_for?. - lib/sms/providers/twilio.rb: form-encoded POST to the Messages API, Basic Auth with SID:Token, treats 201-with-error_code as failure. - lib/sms/providers/voipms.rb: GET with query-string auth, treats status != "success" as failure even on HTTP 200, enforces the API's 160-byte hard cap up front. - lib/sms/providers/signalwire.rb: Twilio-shaped client targeting the per-account Space URL host; strips https:// and trailing / from the user-supplied space URL. - app/controllers/sms_settings_controller.rb: extend the preserve-secret-on-blank-edit pattern to all four providers' password fields via a SECRET_KEYS array. - app/views/sms_settings/index.html.erb: dynamic provider select sourced from Sms::SUPPORTED_PROVIDERS with per-provider field blocks toggled by a nonce'd inline script (the app's CSP requires nonces on inline JS). - SMS.md: new "Configuring …" sections for each provider, wire-format quick-reference table, and updated extension/code-map sections. Co-Authored-By: Claude Opus 4.7 --- SMS.md | 90 +++++++++--- app/controllers/sms_settings_controller.rb | 12 +- app/views/sms_settings/index.html.erb | 156 ++++++++++++++++++--- lib/sms.rb | 53 ++++--- lib/sms/providers/bulkvs.rb | 4 + lib/sms/providers/signalwire.rb | 86 ++++++++++++ lib/sms/providers/twilio.rb | 86 ++++++++++++ lib/sms/providers/voipms.rb | 88 ++++++++++++ 8 files changed, 511 insertions(+), 64 deletions(-) create mode 100644 lib/sms/providers/signalwire.rb create mode 100644 lib/sms/providers/twilio.rb create mode 100644 lib/sms/providers/voipms.rb diff --git a/SMS.md b/SMS.md index 622194ea..48327845 100644 --- a/SMS.md +++ b/SMS.md @@ -1,6 +1,6 @@ # SMS -WaboSign sends signing-invitation SMS via configurable providers. v1 ships [BulkVS](https://www.bulkvs.com) only; the architecture leaves room for additional providers behind the same `Sms.send_message` interface. +WaboSign sends signing-invitation SMS via configurable providers. Supported providers: [BulkVS](https://www.bulkvs.com), [Twilio](https://www.twilio.com), [VoIP.ms](https://voip.ms), and [SignalWire](https://signalwire.com). All providers share the same `Sms.send_message` interface; per-account credentials live in the encrypted `sms_configs` blob and are namespaced by provider. ## What you get @@ -17,13 +17,56 @@ In the [BulkVS portal](https://portal.bulkvs.com/), open the **API** tab and cop In WaboSign: 1. Sign in as an admin → **Settings** → **SMS**. -2. Toggle **Enable SMS** on. +2. Toggle **Enable SMS** on; choose **BulkVS** from the provider dropdown. 3. Paste the BulkVS Basic Auth token. 4. Set **From Number** in E.164 (digits-only with country code, e.g. `15551234567`). 5. *(Optional)* set the **Delivery Status Webhook** to a URL BulkVS will POST status events to. WaboSign does not yet process these inbound events — the field is stored on the config and forwarded to BulkVS so the receipts flow somewhere of your choosing. 6. Save. 7. Use the **Send test** card to verify with a number you own. Errors from BulkVS (bad credentials, malformed number, etc.) come back as `Sms::ProviderError` and are shown inline. +## Configuring Twilio + +In the [Twilio Console](https://console.twilio.com/), open **Account Info** and copy the **Account SID** and **Auth Token** (click *show* to reveal). Buy an SMS-capable number under **Phone Numbers → Manage**. For US long-code delivery you must complete **A2P 10DLC** brand + campaign registration before messages will deliver. + +In WaboSign: +1. **Settings** → **SMS**; pick **Twilio** from the provider dropdown. +2. Paste the Account SID, Auth Token, and Twilio number (full E.164 with leading `+`, e.g. `+15551234567`). +3. Save, then use **Send test**. Twilio returns a message SID on success; a non-null `error_code` is reported as `Sms::ProviderError`. + +## Configuring VoIP.ms + +Set up at the [API portal](https://voip.ms/m/api.php): +1. Set a dedicated **API password** (distinct from your portal login password). +2. Toggle **Enable API** on. +3. Add the WaboSign server's egress IP to the **API IP whitelist**. Without this every call returns `ip_not_authorized` or `invalid_credentials`. +4. Under **Manage DIDs**, enable the **SMS** feature on the DID you plan to send from. + +In WaboSign: +1. **Settings** → **SMS**; pick **VoIP.ms** from the provider dropdown. +2. Fill in your portal-login email (API Username), the API password from step 1 above, and the SMS-enabled DID (digits only, no `+`). +3. Save, then **Send test**. + +Caveats: +- The API hard-caps each call at **160 bytes**, with no segmentation. WaboSign rejects longer bodies up front (`Sms::ProviderError`) rather than truncating. +- VoIP.ms returns HTTP 200 on every error, with `status` indicating the failure code; common codes (`invalid_credentials`, `limit_reached`, `sms_toolong`, `ip_not_authorized`) surface verbatim in the error message. +- Default account quota is 100 SMS/day — contact VoIP.ms support to raise it. + +## Configuring SignalWire + +Open your [SignalWire dashboard](https://signalwire.com/), click the **API** tab in the sidebar, and copy: +- **Your Space URL** (e.g. `acme.signalwire.com`). +- **Your Project ID** (UUID). +- **Your API Token** (`PT…`). Make sure the token has the **Messaging** scope enabled. + +Buy an SMS-capable number under **Phone Numbers**. For US long-code delivery you must attach the number to an approved **10DLC / TCR** campaign. + +In WaboSign: +1. **Settings** → **SMS**; pick **SignalWire** from the provider dropdown. +2. Enter the Space URL (omit `https://`), Project ID, API Token, and From Number (E.164 with leading `+`). +3. Save, then **Send test**. + +A 401 with no obvious credential mistake usually means the API Token lacks the Messaging scope — the upstream message is surfaced verbatim, so check the flash text. + ## How sending happens ``` @@ -44,25 +87,30 @@ Body substitution runs through the existing [`ReplaceEmailVariables`](lib/replac ## Adding another provider -Two-step extension: +Three-step extension: -1. **Implement the provider class** at `lib/sms/providers/.rb`. The interface is `#new(config)` + `#deliver(to:, text:, webhook: nil)`. Raise `Sms::ProviderError` on non-2xx responses. See [`lib/sms/providers/bulkvs.rb`](lib/sms/providers/bulkvs.rb) for shape. +1. **Implement the provider class** at `lib/sms/providers/.rb`. The interface is `self.configured?(config)` + `#new(config)` + `#deliver(to:, text:, webhook: nil)`. Raise `Sms::ProviderError` on non-2xx responses (or on logical failures hidden behind a 200, as VoIP.ms does). See [`lib/sms/providers/bulkvs.rb`](lib/sms/providers/bulkvs.rb), [`twilio.rb`](lib/sms/providers/twilio.rb), [`voipms.rb`](lib/sms/providers/voipms.rb), and [`signalwire.rb`](lib/sms/providers/signalwire.rb) for shape. 2. **Register the provider** in three places: - - `Sms::SUPPORTED_PROVIDERS` in [`lib/sms.rb`](lib/sms.rb). - - The `case provider` switch in `Sms.send_message`. - - The `