Ruby SDK
The official Ruby SDK for Sent provides an elegant, Ruby-idiomatic interface for sending messages. Built with love for Rails applications, featuring comprehensive types & docstrings in Yard, RBS, and RBI.
Requirements
Ruby 3.2.0 or later.
Installation
To use this gem, install via Bundler by adding the following to your application's Gemfile:
gem "sentdm", "~> 0.3.0"Then run:
bundle installOr install directly:
gem install sentdmQuick Start
Initialize the client
require "sentdm"
sent_dm = Sentdm::Client.new(
api_key: ENV["SENT_DM_API_KEY"] # This is the default and can be omitted
)Send your first message
require "sentdm"
sent_dm = Sentdm::Client.new
result = sent_dm.messages.send(
to: ["+1234567890"],
template: {
id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
name: "welcome",
parameters: {
name: "John Doe",
order_id: "12345"
}
},
channels: ["sms", "whatsapp"]
)
puts(result.data.messages[0].id)
puts(result.data.messages[0].status)Authentication
The client reads SENT_DM_API_KEY from the environment by default, or you can pass it explicitly:
require "sentdm"
# Using environment variables
sent_dm = Sentdm::Client.new
# Or explicit configuration
sent_dm = Sentdm::Client.new(
api_key: "your_api_key"
)Send Messages
Send a message
result = sent_dm.messages.send(
to: ["+1234567890"],
template: {
id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
name: "welcome",
parameters: {
name: "John Doe",
order_id: "12345"
}
},
channels: ["sms", "whatsapp"]
)
puts(result.data.messages[0].id)
puts(result.data.messages[0].status)Test mode
Use test_mode: true to validate requests without sending real messages:
result = sent_dm.messages.send(
to: ["+1234567890"],
template: {
id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
name: "welcome"
},
test_mode: true # Validates but doesn't send
)
# Response will have test data
puts(result.data.messages[0].id)
puts(result.data.messages[0].status)Handling errors
When the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 4xx or 5xx response), a subclass of Sentdm::Errors::APIError will be thrown:
begin
result = sent_dm.messages.send(
to: ["+1234567890"],
template: {
id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
name: "welcome",
parameters: {name: "John Doe", order_id: "12345"}
}
)
rescue Sentdm::Errors::APIConnectionError => e
puts("The server could not be reached")
puts(e.cause) # an underlying Exception, likely raised within `net/http`
rescue Sentdm::Errors::RateLimitError => e
puts("A 429 status code was received; we should back off a bit.")
rescue Sentdm::Errors::APIStatusError => e
puts("Another non-200-range status code was received")
puts(e.status)
endError codes are as follows:
| Cause | Error Type |
|---|---|
| HTTP 400 | BadRequestError |
| HTTP 401 | AuthenticationError |
| HTTP 403 | PermissionDeniedError |
| HTTP 404 | NotFoundError |
| HTTP 409 | ConflictError |
| HTTP 422 | UnprocessableEntityError |
| HTTP 429 | RateLimitError |
| HTTP >= 500 | InternalServerError |
| Other HTTP error | APIStatusError |
| Timeout | APITimeoutError |
| Network error | APIConnectionError |
Retries
Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
# Configure the default for all requests:
sent_dm = Sentdm::Client.new(
max_retries: 0 # default is 2
)
# Or, configure per-request:
sent_dm.messages.send(
to: ["+1234567890"],
template: {
id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
name: "welcome"
},
request_options: {max_retries: 5}
)Timeouts
By default, requests will time out after 60 seconds.
# Configure the default for all requests:
sent_dm = Sentdm::Client.new(
timeout: nil # default is 60
)
# Or, configure per-request:
sent_dm.messages.send(
to: ["+1234567890"],
template: {
id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
name: "welcome"
},
request_options: {timeout: 5}
)BaseModel
All parameter and response objects inherit from Sentdm::Internal::Type::BaseModel, which provides several conveniences:
- All fields, including unknown ones, are accessible with
obj[:prop]syntax - Structural equivalence for equality
- Both instances and classes can be pretty-printed
- Helpers such as
#to_h,#deep_to_h,#to_json, and#to_yaml
result = sent_dm.templates.list
# Access fields
template = result.data.data.first
template.name
template[:name] # Same thing
# Convert to hash
template.to_h
# Serialize to JSON
template.to_json
# Pretty print
puts template.inspectContacts
Create and manage contacts:
# Create a contact
result = sent_dm.contacts.create(
phone_number: "+1234567890"
)
puts "Contact ID: #{result.data.id}"
puts "Channels: #{result.data.available_channels}"
# List contacts
result = sent_dm.contacts.list(limit: 100)
result.data.data.each do |contact|
puts "#{contact.phone_number} - #{contact.available_channels}"
end
# Get a contact
result = sent_dm.contacts.get("contact-uuid")
# Update a contact
result = sent_dm.contacts.update(
"contact-uuid",
phone_number: "+1987654321"
)
# Delete a contact
sent_dm.contacts.delete("contact-uuid")Templates
List and retrieve templates:
# List templates
result = sent_dm.templates.list
result.data.data.each do |template|
puts "#{template.name} (#{template.status}): #{template.id}"
puts " Category: #{template.category}"
puts " Channels: #{template.channels.join(', ')}"
end
# Get a specific template
result = sent_dm.templates.get("template-uuid")
puts "Name: #{result.data.name}"
puts "Status: #{result.data.status}"Making custom or undocumented requests
Undocumented properties
You can send undocumented parameters to any endpoint using extra_* options:
result = sent_dm.messages.send(
to: ["+1234567890"],
template: {
id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
name: "welcome"
},
request_options: {
extra_query: {my_query_parameter: value},
extra_body: {my_body_parameter: value},
extra_headers: {"my-header" => value}
}
)
puts(result[:my_undocumented_property])Undocumented endpoints
To make requests to undocumented endpoints:
response = sent_dm.request(
method: :post,
path: '/undocumented/endpoint',
query: {"dog" => "woof"},
headers: {"useful-header" => "interesting-value"},
body: {"hello" => "world"}
)Concurrency & connection pooling
The Sentdm::Client instances are thread-safe, but are only fork-safe when there are no in-flight HTTP requests.
Each instance of Sentdm::Client has its own HTTP connection pool with a default size of 99. As such, we recommend instantiating the client once per application in most settings.
When all available connections from the pool are checked out, requests wait for a new connection to become available, with queue time counting towards the request timeout.
Rails Integration
Configuration
# config/initializers/sentdm.rb
$SENT_DM = Sentdm::Client.new(
api_key: ENV['SENT_DM_API_KEY']
)
# Usage anywhere
$SENT_DM.messages.send(
to: ['+1234567890'],
template: {
id: 'welcome-template',
name: 'welcome'
}
)Controllers
# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
skip_before_action :verify_authenticity_token, only: [:webhook]
def send_welcome
result = $SENT_DM.messages.send(
to: [params[:phone]],
template: {
id: 'welcome-template',
name: 'welcome',
parameters: {name: params[:name]}
}
)
if result.success
render json: {
success: true,
message_id: result.data.messages[0].id,
status: result.data.messages[0].status
}
else
render json: {error: result.error.message}, status: :bad_request
end
end
def webhook
payload = request.body.read
signature = request.headers['X-Webhook-Signature']
# Verify signature
unless $SENT_DM.webhooks.verify_signature(
payload: payload,
signature: signature,
secret: ENV['SENT_DM_WEBHOOK_SECRET']
)
return render json: {error: 'Invalid signature'}, status: :unauthorized
end
# Parse and handle event
event = JSON.parse(payload, object_class: OpenStruct)
case event.type
when 'message.status.updated'
Rails.logger.info "Message #{event.data.id} status: #{event.data.status}"
when 'message.delivered'
# Update database
end
render json: {received: true}
end
endActiveJob Integration
# app/jobs/send_welcome_message_job.rb
class SendWelcomeMessageJob < ApplicationJob
queue_as :default
retry_on Sentdm::Errors::RateLimitError, wait: :polynomially_longer, attempts: 5
discard_on Sentdm::Errors::ValidationError
def perform(user)
result = $SENT_DM.messages.send(
to: [user.phone_number],
template: {
id: 'welcome-template',
name: 'welcome',
parameters: {name: user.first_name}
}
)
raise result.error.message unless result.success
end
endRake Tasks
# lib/tasks/sentdm.rake
namespace :sentdm do
desc "Send test message"
task :test, [:phone, :template] => :environment do |t, args|
args.with_defaults(template: 'welcome-template')
puts "Sending test message to #{args.phone}..."
result = $SENT_DM.messages.send(
to: [args.phone],
template: {
id: args.template,
name: 'welcome'
}
)
if result.success
puts "Sent: #{result.data.messages[0].id}"
else
puts "Failed: #{result.error.message}"
exit 1
end
end
endSinatra Integration
# app.rb
require 'sinatra'
require 'sentdm'
require 'json'
configure do
set :sent_client, Sentdm::Client.new
end
post '/send-message' do
content_type :json
data = JSON.parse(request.body.read)
result = settings.sent_client.messages.send(
to: [data['phone_number']],
template: {
id: data['template_id'],
name: data['template_name'] || 'welcome',
parameters: data['variables'] || {}
}
)
if result.success
{message_id: result.data.messages[0].id, status: result.data.messages[0].status}.to_json
else
status 400
{error: result.error.message}.to_json
end
end
post '/webhooks/sent' do
content_type :json
payload = request.body.read
signature = request.env['HTTP_X_WEBHOOK_SIGNATURE']
# Verify signature
unless settings.sent_client.webhooks.verify_signature(
payload: payload,
signature: signature,
secret: ENV['SENT_DM_WEBHOOK_SECRET']
)
status 401
return {error: 'Invalid signature'}.to_json
end
event = JSON.parse(payload, object_class: OpenStruct)
case event.type
when 'message.delivered'
puts "Message #{event.data.id} delivered!"
when 'message.failed'
puts "Message failed: #{event.data.error.message}"
end
{received: true}.to_json
endSorbet
This library provides comprehensive RBI definitions and has no dependency on sorbet-runtime.
You can provide typesafe request parameters:
sent_dm.messages.send(
to: ["+1234567890"],
template: {
id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
name: "welcome",
parameters: {name: "John Doe"}
}
)Source & Issues
- Version: 0.3.0
- GitHub: sentdm/sent-dm-ruby
- RubyGems: sentdm
- RubyDoc: gemdocs.org/gems/sentdm
- Issues: Report a bug
Getting Help
- Documentation: API Reference
- Troubleshooting: Common Issues
- Support: Email support@sent.dm with your request ID