0.0
Low commit activity in last 3 years
A long-lived project that still receives updates
Rails engine for sending SMS via GTX REST API v2, with delivery status callbacks and metadata persistence
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

>= 1.6

Runtime

>= 6.1, < 9.0
 Project Readme

ShortMessage

Gem Version

ShortMessage is a Rails Engine that sends SMS messages via the GTX Messaging REST API v2 and receives delivery status callbacks.

Requires: Ruby >= 3.2, Rails >= 6.1


Table of Contents

  • ShortMessage
    • Table of Contents
    • Installation
    • Configuration
      • Configuration reference
    • Usage
      • Message attributes
    • Delivery Reports
    • Events
      • short_message.delivered
      • short_message.status_updated
    • Email Notifications
    • Customization
      • Status code labels
      • Extending the request payload
    • Development
      • Testing DLR callbacks locally
    • Building & Releasing
    • Local Development (using in another project)
    • Upgrade Guide (1.x → 2.0)
    • License

Installation

Add to your Gemfile:

gem 'short_message'

Install dependencies:

bundle install

Run the install generator:

rails generate short_message:install

Copy and run the migrations:

bundle exec rails railties:install:migrations FROM=short_message
bundle exec rails db:migrate

Configuration

The generator creates config/initializers/short_message.rb. Edit it to suit your setup:

ShortMessage.configure do |config|
  # --- Required ---
  config.api_base_url    = "https://rest.gtx-messaging.net"
  config.api_key         = ENV.fetch("GTX_API_KEY")
  config.response_format = "json"   # "json", "xml", or "plain"

  # --- SMS defaults ---
  config.default_sms_sender = "MyApp"   # sender name or number used when none is set on the message

  # --- Delivery reports (DLR) ---
  config.dlr_mask        = 3            # 1 = delivered, 2 = undelivered, 3 = both
  config.callback_token  = ENV["SHORT_MESSAGE_CALLBACK_TOKEN"]
  config.dlr_url         = "https://example.com/short_message/messages/status?token=#{config.callback_token}"

  # --- HTTP timeouts ---
  config.open_timeout = 5    # seconds
  config.read_timeout = 30   # seconds

  # --- Email notifications (optional) ---
  config.default_mail_sender       = "sms@example.com"
  config.admin_notification_email  = "admin@example.com"   # set nil to disable error emails
  config.reload_notification_email = "billing@example.com" # notified on HTTP 402 from GTX
end

Configuration reference

Key Required Default Description
api_base_url yes "https://rest.gtx-messaging.net" GTX API endpoint
api_key yes GTX API authentication key
response_format no "json" "json", "xml", or "plain"
default_sms_sender no Fallback sender when none is set on the message
dlr_mask no 3 Delivery report event mask
dlr_url no Callback URL GTX posts status updates to
callback_token no Token validated on incoming status callbacks
open_timeout no 5 HTTP connection open timeout (seconds)
read_timeout no 30 HTTP read timeout (seconds)
default_mail_sender no From address for notification emails
admin_notification_email no Receives error notifications; set nil to disable
reload_notification_email no Receives payment-required (HTTP 402) alerts

Usage

Create a ShortMessage::Message and call deliver:

sms = ShortMessage::Message.new(
  sender:    "+41791234567",   # or a name up to 11 chars
  recipient: "+41799876543",
  text:      "Hello World!"
)

sms.deliver   # => true on success, false on failure

sender is optional when config.default_sms_sender is configured.

Recipient normalization: if recipient starts with 00, the gem automatically converts it to + format (for example 0041791234567 -> +41791234567). Any other non-E.164 recipient format is rejected before sending.

After a successful delivery, sms.message_key holds the provider-assigned message ID and sms.provider_http_status holds the HTTP status code returned by GTX.

Message attributes

Attribute Description
sender SMS sender name or number
recipient SMS recipient phone number in E.164 (+...)
text Message body
message_key Unique ID assigned by GTX after delivery
status_code Delivery status code (updated via DLR callback)
provider_http_status HTTP status from GTX at delivery time
provider_status Status string from GTX response
provider_response_body Full raw response payload from GTX
error_code Error code if delivery failed
error_message Error description if delivery failed
submitted_at Timestamp when message was submitted to GTX
delivered_at Timestamp when delivery confirmed

Delivery Reports

ShortMessage mounts a status-update endpoint at /short_message/messages/status. Configure GTX to POST (or GET) to this URL with the message ID and status:

POST /short_message/messages/status?id=<message_key>&status=<code>&token=<callback_token>
Parameter Description
id The message_key of the message to update
status Numeric status code from GTX
token Required when config.callback_token is set

Response codes

HTTP Meaning
200 Status updated successfully
400 Missing id or status parameter
401 Invalid or missing callback token
404 Message not found

When config.callback_token is set, every incoming callback must include token=<value> or it will be rejected with a 401.


Events

ShortMessage fires ActiveSupport::Notifications events so you can react to delivery and status changes without monkey-patching.

short_message.delivered

Fired after a message is successfully sent to GTX.

ActiveSupport::Notifications.subscribe('short_message.delivered') do |name, start, finish, id, payload|
  Rails.logger.info "SMS #{payload[:key]} delivered"
end

Payload keys: key (the message_key assigned by GTX).

short_message.status_updated

Fired when a delivery report callback is received.

ActiveSupport::Notifications.subscribe('short_message.status_updated') do |name, start, finish, id, payload|
  Activity.create(
    message: "Message #{payload[:key]} status → #{payload[:status]}"
  )
end

Payload keys: key (message ID), status (new status code).


Email Notifications

When admin_notification_email is configured, ShortMessage sends an email on delivery failure or unexpected exceptions. Set it to nil to disable.

When reload_notification_email is configured, ShortMessage sends an email when GTX returns HTTP 402 (payment required / credit exhausted).


Customization

Status code labels

Status code descriptions are stored in locale files under config/locales/. Add or override keys there to customise the text returned by message.status_text.

# config/locales/short_message.en.yml
en:
  short_message:
    status:
      code_1:  "Delivered to handset"
      code_2:  "Delivery failed"
      code_4:  "Queued on SMSC"
      code_8:  "Delivered to SMSC"
      code_16: "Not delivered to SMSC"
sms.status_text   # => "Delivered to handset"

Extending the request payload

Override build_deliver_params_hash in an initializer to add or change parameters sent to GTX:

ShortMessage::Message.module_eval do
  private

  def build_deliver_params_hash
    super.merge("coding" => 2)   # e.g. enable UCS-2 encoding
  end
end

Development

Clone the repo and install dependencies:

git clone https://github.com/asaurer/short_message.git
cd short_message
bundle install

Run the test suite (migrations run automatically):

bundle exec rake test

Testing DLR callbacks locally

GTX needs a publicly reachable HTTPS URL to deliver status callbacks. Use ngrok to expose your local server:

ngrok http 3000

Then configure the tunnel URL in your initializer:

config.callback_token = "dev-secret"
config.dlr_url = "https://abc123.ngrok.io/short_message/messages/status?token=dev-secret"

Restart your Rails server after changing the initializer. Once a message is delivered, GTX will POST the status update to the ngrok URL which forwards it to your local app.

You can also simulate a callback manually without ngrok:

curl -X POST "http://localhost:3000/short_message/messages/status" \
  -d "id=YOUR_MESSAGE_KEY&status=1&token=dev-secret"

Replace YOUR_MESSAGE_KEY with the value of sms.message_key after calling sms.deliver.


Building & Releasing

Build the gem locally:

gem build short_message.gemspec

This creates short_message-<version>.gem in the project root.

Install the built gem locally to verify it works:

gem install short_message-<version>.gem

Push to RubyGems:

gem push short_message-<version>.gem

You need a RubyGems account with push access to the short_message gem. Credentials are stored in ~/.gem/credentials.

Release checklist:

  1. Update lib/short_message/version.rb with the new version number
  2. Update CHANGELOG.md with the release notes
  3. Commit the version bump and changelog
  4. Build and push the gem
  5. Tag the release: git tag v<version> && git push origin v<version>

Local Development (using in another project)

When you want to work on short_message and test changes inside a host Rails app simultaneously, point the host app's Gemfile at your local checkout instead of RubyGems:

# host-app/Gemfile
gem 'short_message', path: '/path/to/short_message'

Then in the host app:

bundle install

Bundler symlinks the gem from the local path, so every change you make in the short_message directory is picked up immediately on the next request (or reload! in the console) without re-running bundle install.

If the gem has pending migrations that need to land in the host app's database:

bundle exec rails railties:install:migrations FROM=short_message
bundle exec rails db:migrate

To re-generate the initializer or locales after making changes to the generator templates:

bundle exec rails generate short_message:install

When you are done and want to switch back to the published gem, restore the Gemfile entry:

gem 'short_message', '~> 2.0'

Upgrade Guide (1.x → 2.0)

  1. Replace old GTX config keys with api_base_url, api_key, and response_format.

  2. Remove any initializer override of build_deliver_params_string (renamed to build_deliver_params_hash).

  3. Install and run the new migration:

    bundle exec rails railties:install:migrations FROM=short_message
    bundle exec rails db:migrate

    If the short_message_messages table already exists you will see Mysql2::Error: Table 'short_message_messages' already exists. This happens because railties:install:migrations re-copies the original create migration under a new timestamp. Mark it as already applied, then migrate:

    bundle exec rails runner \
      "ActiveRecord::Base.connection.execute(
        \"INSERT IGNORE INTO schema_migrations (version) \" \
        \"VALUES ('$(ls db/migrate/*create_short_message_messages.short_message.rb \
          | grep -oP '[0-9]+(?=_)')' )\")"
    bundle exec rails db:migrate

    SQLite users: replace INSERT IGNORE with INSERT OR IGNORE.

  4. Optionally refresh the initializer and locales:

    bundle exec rails generate short_message:install
  5. Configure delivery reports — set config.dlr_url to your callback endpoint and config.callback_token for request validation:

    config.callback_token = ENV["SHORT_MESSAGE_CALLBACK_TOKEN"]
    config.dlr_url        = "https://your-app.example/short_message/messages/status?token=#{config.callback_token}"

    In the GTX portal, ensure delivery report callbacks are enabled for your API key and that your app's callback URL is publicly reachable over HTTPS.


License

MIT — see MIT-LICENSE.