Ruby SDK
broadcast-ruby is the official Ruby client for the Broadcast API. It covers every
API endpoint, and it works against any Broadcast instance — there is no hosted-only
behaviour.
The gem is the reference implementation: the PHP SDK and the other clients are built to match its behaviour.
Installation
Add it to your Gemfile:
gem 'broadcast-ruby'
Or install it directly:
gem install broadcast-ruby
For plain Ruby scripts, require it explicitly:
require 'broadcast'
Quick Start
Create a client with your API token and the URL of your instance:
client = Broadcast::Client.new( api_token: ENV['BROADCAST_API_TOKEN'], host: 'https://mail.example.com' ) client.subscribers.create(email: 'ada@example.com', first_name: 'Ada')
Warning
host is required — there is no default. Broadcast is self-hosted first, so
every instance lives at its own domain. A built-in guess would be wrong for almost
everyone, so the gem asks you to be explicit.
You can also set BROADCAST_HOST and BROADCAST_API_TOKEN in the environment.
These are the same names the Agents CLI uses in
~/.config/broadcast/config, so a machine already set up for the CLI needs no extra
configuration.
Create your API token under Settings → API Keys, granting only the permissions your integration needs. See API Authentication for the full permission list.
Configuration
client = Broadcast::Client.new( api_token: '...', host: 'https://mail.example.com', timeout: 30, # read timeout, seconds open_timeout: 10, retry_attempts: 3, retry_delay: 1, # base backoff, multiplied by attempt number max_retry_delay: 30, # ceiling on a server-supplied Retry-After warnings_mode: :log, # :log | :raise | :ignore logger: Rails.logger, broadcast_channel_id: 42 # admin/system tokens only )
Timeouts, rate-limited requests, and server errors are retried with backoff. A validation error (422) is never retried — it is deterministic, so a retry only adds latency.
Responses
Every call returns the parsed JSON body, so result['id'] works as you would
expect. Transport metadata is available alongside it:
result = client.subscribers.create(email: 'ada@example.com') result['id'] # the response body result.status # 201 result.warnings # parsed warnings, if any result.rate_limit&.remaining # requests left in the window result.idempotent_replay? # true if the API replayed a stored response
Warnings
A successful response can still carry warnings — the API accepted your request but
ignored part of it, such as an unrecognised parameter or a filter it could not
parse. A mistyped created_after silently widens a result set rather than failing,
so it is worth checking.
result.warnings.each { |w| Rails.logger.warn(w) }
Set warnings_mode: :raise to turn them into exceptions instead. Note the write has
already happened by then — nothing is rolled back. See
API Response Warnings.
Rate Limits
Every response carries the current limit state, and 429s are retried
automatically, honouring the server’s Retry-After (capped at max_retry_delay).
result = client.subscribers.list result.rate_limit.limit # => 120 result.rate_limit.remaining # => 118 result.rate_limit.reset # => 2026-07-26 12:00:00 UTC sleep 1 if result.rate_limit.remaining < 10
If the retries are exhausted you get a Broadcast::RateLimitError, which carries
#retry_after so you can requeue the job sensibly.
Rails Integration
The gem ships an ActionMailer delivery method, so existing mailers can send through Broadcast without changing a single mailer class:
# config/environments/production.rb config.action_mailer.delivery_method = :broadcast config.action_mailer.broadcast_settings = { api_token: ENV['BROADCAST_API_TOKEN'], host: 'https://mail.example.com' }
Your mailers then work unchanged:
class OrderMailer < ApplicationMailer def receipt(order) @order = order mail(to: order.email, subject: 'Your receipt') end end
This only needs a token with transactional permissions.
Common Tasks
Subscribers
client.subscribers.list(page: 1, is_active: true, tags: ['vip']) client.subscribers.find(email: 'ada@example.com') client.subscribers.create(email: 'ada@example.com', tags: ['vip']) client.subscribers.update('ada@example.com', first_name: 'Ada') client.subscribers.add_tags('ada@example.com', ['beta']) client.subscribers.unsubscribe('ada@example.com')
See Subscribers API for every filter and field.
Transactional email
client.transactionals.create( to: 'ada@example.com', subject: 'Your receipt', body: '<p>Thanks for your order.</p>', idempotency_key: "receipt-#{order.id}" )
Passing an idempotency_key makes a retry safe: the server stores the response for
24 hours and replays it rather than sending a second email. Check
result.idempotent_replay? to tell a replay from a fresh send.
Full details in Transactional Email API.
Broadcasts
broadcast = client.broadcasts.create(subject: 'Weekly update', body: '<p>Hello</p>') client.broadcasts.schedule(broadcast['id'], scheduled_send_at: '2026-08-01T09:00:00Z', scheduled_timezone: 'UTC')
Warning
client.broadcasts.send_broadcast(id) sends immediately to the whole audience.
There is no unsend. Call client.status first — if readiness.broadcasts is
false, the channel has no usable email server or sender identity.
Channel Scoping
Admin (system) tokens can address any channel. Scope a block of calls to one:
client.with_channel(123) do client.email_servers.list end
The previous scope is restored afterwards, including when the block raises. Regular tokens are already channel-scoped and need none of this.
Webhooks
class WebhooksController < ApplicationController skip_before_action :verify_authenticity_token def create payload = request.raw_post unless Broadcast::Webhook.verify(payload, request.headers['X-Broadcast-Signature'], request.headers['X-Broadcast-Timestamp'], secret: ENV['BROADCAST_WEBHOOK_SECRET']) return head :unauthorized end event = JSON.parse(payload) # handle event['type'] head :ok end end
Pass the raw request body. Re-serialising a parsed hash changes the bytes and
verification will fail. Broadcast::Webhook::EVENT_TYPES lists every event name.
See Webhook Endpoints API for creating and managing endpoints.
Export & Migration
Admin (system) tokens can read the migration endpoints, which back instance backups and moves:
client = Broadcast::Client.new(api_token: '...', host: '...', broadcast_channel_id: 42) client.migration.manifest # sizes the export first client.migration.each_record(:subscribers) do |subscriber| csv << subscriber end
each_record pages automatically and stops when the server reports there is no more
data. See Backup & Recovery.
Errors
begin client.subscribers.create(email: 'invalid') rescue Broadcast::ValidationError => e # 422 # bad input — do not retry rescue Broadcast::AuthenticationError => e # 401 rescue Broadcast::AuthorizationError => e # 403 — token lacks permission rescue Broadcast::NotFoundError => e # 404 rescue Broadcast::RateLimitError => e # 429 sleep(e.retry_after || 5) rescue Broadcast::APIError => e # 5xx and other transport failures end
Source and Support
The gem is open source and its README documents every method:
- Source: github.com/send-broadcast/broadcast-ruby
- RubyGems: rubygems.org/gems/broadcast-ruby
Prefer PHP? See the PHP SDK.