Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,45 @@ rescue Customerio::InvalidResponse => e
end
```

### Trigger Broadcasts

You can trigger [API-triggered broadcasts](https://customer.io/docs/api-triggered-broadcasts/) using the `APIClient`. Create a `TriggerBroadcastRequest` with the broadcast's numeric ID and optional audience/data parameters.

```ruby
require "customerio"

client = Customerio::APIClient.new("your API key", region: Customerio::Regions::US)

request = Customerio::TriggerBroadcastRequest.new(
broadcast_id: 12,
emails: ["recipient@example.com"],
data: {
headline: "Roadrunner spotted in Albuquerque!",
date: 1511315635,
},
email_add_duplicates: false,
email_ignore_missing: false,
id_ignore_missing: false,
)

begin
response = client.trigger_broadcast(request)
puts response
rescue Customerio::InvalidResponse => e
puts e.code, e.message
end
```

You can target the broadcast audience in several ways. Only one audience option can be present per request:

- `recipients`: a hash with filter conditions (e.g., `{ segment: { id: 7 } }`)
- `emails`: an array of email addresses
- `ids`: an array of customer IDs
- `per_user_data`: an array of per-user objects
- `data_file_url`: a URL to a JSON lines file

If you omit the audience option, the broadcast uses its default audience configured in the UI.

## Contributing

1. Fork it
Expand Down
1 change: 1 addition & 0 deletions lib/customerio.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module Customerio
require "customerio/requests/send_sms_request"
require "customerio/requests/send_inbox_message_request"
require "customerio/requests/send_in_app_request"
require "customerio/requests/trigger_broadcast_request"
require "customerio/api"
require "customerio/param_encoder"
end
10 changes: 10 additions & 0 deletions lib/customerio/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ def send_in_app(req)
deliver(send_in_app_path, req.message)
end

def trigger_broadcast(req)
validate_request!(req, TriggerBroadcastRequest)

deliver(trigger_broadcast_path(req.broadcast_id), req.message)
end

private

def deliver(path, message)
Expand Down Expand Up @@ -87,5 +93,9 @@ def send_inbox_message_path
def send_in_app_path
"/v1/send/in_app"
end

def trigger_broadcast_path(broadcast_id)
"/v1/campaigns/#{broadcast_id}/triggers"
end
Comment thread
hownowstephen marked this conversation as resolved.
end
end
28 changes: 28 additions & 0 deletions lib/customerio/requests/trigger_broadcast_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Customerio
class TriggerBroadcastRequest
AUDIENCE_FIELDS = %i[recipients emails ids per_user_data data_file_url].freeze

OPTIONAL_FIELDS = %i[data email_add_duplicates email_ignore_missing id_ignore_missing].freeze

attr_reader :broadcast_id, :message

def initialize(opts)
raise ArgumentError, "broadcast_id is required" unless opts.key?(:broadcast_id)
raise ArgumentError, "broadcast_id must be an integer" unless opts[:broadcast_id].is_a?(Integer)

@broadcast_id = opts[:broadcast_id]
@message = opts.select { |field, _value| valid_field?(field) }

audience = AUDIENCE_FIELDS.select { |field| @message.key?(field) }
raise ArgumentError, "only one of #{AUDIENCE_FIELDS.join(', ')} can be present" if audience.length > 1
end

private

def valid_field?(field)
OPTIONAL_FIELDS.include?(field) || AUDIENCE_FIELDS.include?(field)
end
end
end
123 changes: 123 additions & 0 deletions spec/api_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -429,4 +429,127 @@ def json(data)
)
end
end

describe "#trigger_broadcast" do
it "sends a POST request to the broadcast triggers path" do
req = Customerio::TriggerBroadcastRequest.new(
broadcast_id: 12,
data: { headline: "Test" },
recipients: { segment: { id: 7 } },
)

stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
.with(headers: request_headers, body: req.message)
.to_return(status: 200, body: { trigger_id: "abc123" }.to_json, headers: {})

client.trigger_broadcast(req).should eq({ "trigger_id" => "abc123" })
end

it "sends with email list audience" do
req = Customerio::TriggerBroadcastRequest.new(
broadcast_id: 12,
emails: ["a@example.com", "b@example.com"],
email_add_duplicates: false,
email_ignore_missing: true,
)

stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
.with(headers: request_headers, body: req.message)
.to_return(status: 200, body: { trigger_id: "abc123" }.to_json, headers: {})

client.trigger_broadcast(req).should eq({ "trigger_id" => "abc123" })
end

it "sends with id list audience" do
req = Customerio::TriggerBroadcastRequest.new(
broadcast_id: 12,
ids: [1, 2, 3],
id_ignore_missing: true,
)

stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
.with(headers: request_headers, body: req.message)
.to_return(status: 200, body: { trigger_id: "abc123" }.to_json, headers: {})

client.trigger_broadcast(req).should eq({ "trigger_id" => "abc123" })
end

it "sends with data_file_url audience" do
req = Customerio::TriggerBroadcastRequest.new(
broadcast_id: 12,
data_file_url: "https://example.com/data.json",
)

stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
.with(headers: request_headers, body: req.message)
.to_return(status: 200, body: { trigger_id: "abc123" }.to_json, headers: {})

client.trigger_broadcast(req).should eq({ "trigger_id" => "abc123" })
end

it "raises an error when broadcast_id is missing" do
lambda {
Customerio::TriggerBroadcastRequest.new(data: { headline: "Test" })
}.should raise_error(ArgumentError, "broadcast_id is required")
end

it "raises an error when broadcast_id is not an integer" do
lambda {
Customerio::TriggerBroadcastRequest.new(broadcast_id: "12")
}.should raise_error(ArgumentError, "broadcast_id must be an integer")
end

it "raises an error when multiple audience fields are provided" do
lambda {
Customerio::TriggerBroadcastRequest.new(
broadcast_id: 12,
emails: ["a@example.com"],
ids: [1, 2],
)
}.should raise_error(ArgumentError, /only one of/)
end

it "raises an error when request is not a TriggerBroadcastRequest" do
lambda {
client.trigger_broadcast("not a request")
}.should raise_error(ArgumentError, /must be an instance of/)
end

it "handles validation failures (400)" do
req = Customerio::TriggerBroadcastRequest.new(
broadcast_id: 12,
emails: ["a@example.com"],
)

err_json = { meta: { error: "example error" } }.to_json

stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
.with(headers: request_headers, body: req.message)
.to_return(status: 400, body: err_json, headers: {})

lambda { client.trigger_broadcast(req) }.should(
raise_error(Customerio::InvalidResponse) { |error|
error.message.should eq "example error"
error.code.should eq "400"
}
)
end

it "handles other failures (5xx)" do
req = Customerio::TriggerBroadcastRequest.new(
broadcast_id: 12,
)

stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
.with(headers: request_headers, body: req.message)
.to_return(status: 500, body: "Server unavailable", headers: {})

lambda { client.trigger_broadcast(req) }.should(
raise_error(Customerio::InvalidResponse) { |error|
error.message.should eq "Server unavailable"
error.code.should eq "500"
}
)
end
end
end