13 KiB
SMS
WaboSign sends signing-invitation SMS via configurable providers. Supported providers: BulkVS, Twilio, VoIP.ms, and SignalWire. 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
- A self-serve
/settings/smspage where admins paste their BulkVS Basic Auth header and From number. - A Send test card on the same page for verifying the config with a one-off SMS to any phone number.
- Per-submitter Send SMS button on the submission detail page (
/submissions/:id). - "Send SMS on save" toggle in the submitter edit dialog — already wired into
SubmittersController#maybe_resend_email_sms, now actually fires the job. - Per-account body template override at
/settings/personalization→ "Signature request SMS". Supports{account.name},{submitter.link},{submitter.name},{submitter.first_name},{submission.name},{sender.name}and the rest of the existingReplaceEmailVariablesvocabulary. - Sidekiq job (
SendSubmitterInvitationSmsJob, retry: 5) so a failed BulkVS request retries on its own.
Configuring BulkVS
In the BulkVS portal, open the API tab and copy the pre-encoded Basic Auth Header value (it's a single base64 string like dXNlcjp0b2tlbg==; do NOT include the literal Basic prefix).
In WaboSign:
- Sign in as an admin → Settings → SMS.
- Toggle Enable SMS on; choose BulkVS from the provider dropdown.
- Paste the BulkVS Basic Auth token.
- Set From Number in E.164 (digits-only with country code, e.g.
15551234567). - (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.
- Save.
- Use the Send test card to verify with a number you own. Errors from BulkVS (bad credentials, malformed number, etc.) come back as
Sms::ProviderErrorand are shown inline.
Configuring Twilio
In the Twilio Console, 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:
- Settings → SMS; pick Twilio from the provider dropdown.
- Paste the Account SID, Auth Token, and Twilio number (full E.164 with leading
+, e.g.+15551234567). - Save, then use Send test. Twilio returns a message SID on success; a non-null
error_codeis reported asSms::ProviderError.
Configuring VoIP.ms
Set up at the API portal:
- Set a dedicated API password (distinct from your portal login password).
- Toggle Enable API on.
- Add the WaboSign server's egress IP to the API IP whitelist. Without this every call returns
ip_not_authorizedorinvalid_credentials. - Under Manage DIDs, enable the SMS feature on the DID you plan to send from.
In WaboSign:
- Settings → SMS; pick VoIP.ms from the provider dropdown.
- Fill in your portal-login email (API Username), the API password from step 1 above, and the SMS-enabled DID (digits only, no
+). - 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
statusindicating 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, 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:
- Settings → SMS; pick SignalWire from the provider dropdown.
- Enter the Space URL (omit
https://), Project ID, API Token, and From Number (E.164 with leading+). - 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
User clicks "Send SMS" on /submissions/:id
→ SubmittersSendSmsController#create
→ SendSubmitterInvitationSmsJob.perform_async (Sidekiq, retry: 5)
→ Sms.send_message(account:, to:, text:)
→ Sms::Providers::Bulkvs#deliver
→ HTTPS POST to https://portal.bulkvs.com/api/v1.0/messageSend
→ returns BulkVS JSON
→ SubmissionEvent.create!(event_type: 'send_sms')
→ Submitter.sent_at ||= Time.current
The "send SMS on save" toggle in the submitter edit dialog takes the same code path via SubmittersController#maybe_resend_email_sms.
Body substitution runs through the existing ReplaceEmailVariables module. The account-level template at submitter_invitation_sms overrides the i18n default submitter_invitation_sms_body_sign when set.
Adding another provider
Three-step extension:
-
Implement the provider class at
lib/sms/providers/<name>.rb. The interface isself.configured?(config)+#new(config)+#deliver(to:, text:, webhook: nil). RaiseSms::ProviderErroron non-2xx responses (or on logical failures hidden behind a 200, as VoIP.ms does). Seelib/sms/providers/bulkvs.rb,twilio.rb,voipms.rb, andsignalwire.rbfor shape. -
Register the provider in three places:
- Append to
Sms::SUPPORTED_PROVIDERSinlib/sms.rb. - Add a branch to
Sms.provider_classin the same file. - Add a
data-provider-block="..."section toapp/views/sms_settings/index.html.erbwith the credential fields, and add the human-readable label to theprovider_labelshash at the top of the template.
- Append to
-
If the new provider has a secret field that should be preserved on blank-edits (the way BulkVS's Basic Auth token is), add its config key to
SmsSettingsController::SECRET_KEYS.
Per-provider config fields all ride on the same sms_configs EncryptedConfig hash; prefix new keys with the provider name (e.g. twilio_auth_token) to avoid collisions. The Sidekiq job and per-submitter controller are provider-agnostic and don't need to change.
Code map
| File | Role |
|---|---|
| lib/sms.rb | Top-level Sms module: enabled_for?(account), configuration_for(account), send_message, normalize_phone, provider_class. Error types: Sms::Error, Sms::NotConfiguredError, Sms::ProviderError, Sms::InvalidNumberError. |
| lib/sms/providers/bulkvs.rb | BulkVS HTTPS client. Constructs Basic Auth + JSON body, raises Sms::ProviderError with the upstream error message on non-2xx. |
| lib/sms/providers/twilio.rb | Twilio Messages API client (form-encoded body, Basic Auth with SID:Token, 201 on success). |
| lib/sms/providers/voipms.rb | VoIP.ms REST/JSON sendSMS client (GET with query-string auth, treats status != "success" as failure even on HTTP 200, enforces 160-byte cap up front). |
| lib/sms/providers/signalwire.rb | SignalWire Compatibility API client (Twilio-shaped form body, per-account Space URL host, requires Messaging-scoped API Token). |
| app/jobs/send_submitter_invitation_sms_job.rb | Sidekiq job. Skips if submitter has no phone, is completed, archived, or the account has no SMS config. |
| app/controllers/sms_settings_controller.rb | index / create / test_message. SECRET_KEYS lists the password-field config keys that should be preserved on blank-edits. |
| app/controllers/submitters_send_sms_controller.rb | create action behind the per-submitter Send SMS button. Mirrors SubmittersSendEmailController. |
| app/views/sms_settings/index.html.erb | Settings form + test-send card. |
| app/views/submissions/_send_sms_button.html.erb | Per-submitter Send SMS button. Disabled with a tooltip when the provider is unconfigured or the submitter has no phone. |
| app/views/submissions/_send_sms.html.erb | "Send SMS on save" toggle rendered inside the submitter edit dialog. |
| app/views/personalization_settings/_signature_request_sms_form.html.erb | Per-account SMS body override form. |
| app/models/encrypted_config.rb | SMS_CONFIGS_KEY = 'sms_configs' added to CONFIG_KEYS. |
| app/models/account_config.rb | SUBMITTER_INVITATION_SMS_KEY = 'submitter_invitation_sms'. |
| config/routes.rb | resources :sms with index, create, and post :test_message on the collection; submitters nested resources :send_sms. |
Provider wire-format quick reference
| Provider | Endpoint | Body encoding | Phone format | Success signal |
|---|---|---|---|---|
| BulkVS | POST https://portal.bulkvs.com/api/v1.0/messageSend |
JSON | digits, no + |
HTTP 2xx |
| Twilio | POST https://api.twilio.com/2010-04-01/Accounts/<SID>/Messages.json |
application/x-www-form-urlencoded |
digits with + |
HTTP 201 and error_code is null |
| VoIP.ms | GET https://voip.ms/api/v1/rest.php?method=sendSMS&… |
query string | digits, no + |
HTTP 200 and status == "success" |
| SignalWire | POST https://<space>/api/laml/2010-04-01/Accounts/<ProjectID>/Messages |
application/x-www-form-urlencoded |
digits with + |
HTTP 201 and error_code is null |
BulkVS API reference
- Endpoint:
POST https://portal.bulkvs.com/api/v1.0/messageSend - Auth:
Authorization: Basic <pre-encoded token from the BulkVS portal> - Content-Type:
application/json - Body:
{ "From": "15551234567", "To": ["15555550100"], "Message": "Hello from WaboSign", "delivery_status_webhook_url": "https://your-app.example/webhooks/sms" } - Response: 2xx JSON on success; non-2xx with
Description/Status/errorkeys on failure. - Docs: https://portal.bulkvs.com/api/v1.0/documentation (login required for the Swagger UI).
Verified during commit 1872a099
/settings/smsreturns 200; all six form fields render./settings/personalizationrenders the SMS body editor with the full variable list.- With a bogus saved token,
Sms.send_messageopens an HTTPS connection to BulkVS, receives a real 401, and surfaces it asSms::ProviderError("BulkVS rejected request (HTTP 401): …"). Proves the transport + auth header + JSON body shape are correct end-to-end; only the test token is wrong. - Route helpers resolve:
settings_sms_path(no_index_because "sms" is uncountable in Rails inflection),test_message_settings_sms_path,submitter_send_sms_path.
Out of scope
- No inbound delivery-status webhook handler. BulkVS / Twilio / SignalWire can be told where to POST receipts (
delivery_status_webhook_url/StatusCallback), but WaboSign does not yet consume those POSTs. Add a controller at e.g.app/controllers/webhooks/sms_delivery_controller.rbif you want delivery confirmations in the audit trail orSubmissionEventlog. - No MMS. Outbound is text-only across all providers.
- No client-side message segmentation. BulkVS / Twilio / SignalWire auto-segment on their end and bill per segment; VoIP.ms refuses bodies over 160 bytes outright — WaboSign raises
Sms::ProviderErrorup front for that provider instead of attempting to split. - Phone validation is minimal —
Sms.normalize_phonestrips non-digits and rejects strings shorter than 8 digits. Each provider prepends+if its wire format requires it. Malformed numbers surface asSms::ProviderErrorwith the upstream message. - No rate-limiting / per-account quota. Relies on each provider's own controls.