Skip to content

Commit 481f4f4

Browse files
Add broadcast trigger support to APIClient (#137)
* Add trigger_broadcast to APIClient Adds TriggerBroadcastRequest and APIClient#trigger_broadcast for the POST /v1/campaigns/{broadcast_id}/triggers endpoint. Supports all audience options (recipients, emails, ids, per_user_data, data_file_url) with validation that only one audience type is provided per request. Closes #100
1 parent 7797fb0 commit 481f4f4

5 files changed

Lines changed: 201 additions & 0 deletions

File tree

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,45 @@ rescue Customerio::InvalidResponse => e
401401
end
402402
```
403403

404+
### Trigger Broadcasts
405+
406+
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.
407+
408+
```ruby
409+
require "customerio"
410+
411+
client = Customerio::APIClient.new("your API key", region: Customerio::Regions::US)
412+
413+
request = Customerio::TriggerBroadcastRequest.new(
414+
broadcast_id: 12,
415+
emails: ["recipient@example.com"],
416+
data: {
417+
headline: "Roadrunner spotted in Albuquerque!",
418+
date: 1511315635,
419+
},
420+
email_add_duplicates: false,
421+
email_ignore_missing: false,
422+
id_ignore_missing: false,
423+
)
424+
425+
begin
426+
response = client.trigger_broadcast(request)
427+
puts response
428+
rescue Customerio::InvalidResponse => e
429+
puts e.code, e.message
430+
end
431+
```
432+
433+
You can target the broadcast audience in several ways. Only one audience option can be present per request:
434+
435+
- `recipients`: a hash with filter conditions (e.g., `{ segment: { id: 7 } }`)
436+
- `emails`: an array of email addresses
437+
- `ids`: an array of customer IDs
438+
- `per_user_data`: an array of per-user objects
439+
- `data_file_url`: a URL to a JSON lines file
440+
441+
If you omit the audience option, the broadcast uses its default audience configured in the UI.
442+
404443
## Contributing
405444

406445
1. Fork it

lib/customerio.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module Customerio
1111
require "customerio/requests/send_sms_request"
1212
require "customerio/requests/send_inbox_message_request"
1313
require "customerio/requests/send_in_app_request"
14+
require "customerio/requests/trigger_broadcast_request"
1415
require "customerio/api"
1516
require "customerio/param_encoder"
1617
end

lib/customerio/api.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ def send_in_app(req)
4646
deliver(send_in_app_path, req.message)
4747
end
4848

49+
def trigger_broadcast(req)
50+
validate_request!(req, TriggerBroadcastRequest)
51+
52+
deliver(trigger_broadcast_path(req.broadcast_id), req.message)
53+
end
54+
4955
private
5056

5157
def deliver(path, message)
@@ -87,5 +93,9 @@ def send_inbox_message_path
8793
def send_in_app_path
8894
"/v1/send/in_app"
8995
end
96+
97+
def trigger_broadcast_path(broadcast_id)
98+
"/v1/campaigns/#{broadcast_id}/triggers"
99+
end
90100
end
91101
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# frozen_string_literal: true
2+
3+
module Customerio
4+
class TriggerBroadcastRequest
5+
AUDIENCE_FIELDS = %i[recipients emails ids per_user_data data_file_url].freeze
6+
7+
OPTIONAL_FIELDS = %i[data email_add_duplicates email_ignore_missing id_ignore_missing].freeze
8+
9+
attr_reader :broadcast_id, :message
10+
11+
def initialize(opts)
12+
raise ArgumentError, "broadcast_id is required" unless opts.key?(:broadcast_id)
13+
raise ArgumentError, "broadcast_id must be an integer" unless opts[:broadcast_id].is_a?(Integer)
14+
15+
@broadcast_id = opts[:broadcast_id]
16+
@message = opts.select { |field, _value| valid_field?(field) }
17+
18+
audience = AUDIENCE_FIELDS.select { |field| @message.key?(field) }
19+
raise ArgumentError, "only one of #{AUDIENCE_FIELDS.join(', ')} can be present" if audience.length > 1
20+
end
21+
22+
private
23+
24+
def valid_field?(field)
25+
OPTIONAL_FIELDS.include?(field) || AUDIENCE_FIELDS.include?(field)
26+
end
27+
end
28+
end

spec/api_client_spec.rb

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,4 +429,127 @@ def json(data)
429429
)
430430
end
431431
end
432+
433+
describe "#trigger_broadcast" do
434+
it "sends a POST request to the broadcast triggers path" do
435+
req = Customerio::TriggerBroadcastRequest.new(
436+
broadcast_id: 12,
437+
data: { headline: "Test" },
438+
recipients: { segment: { id: 7 } },
439+
)
440+
441+
stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
442+
.with(headers: request_headers, body: req.message)
443+
.to_return(status: 200, body: { trigger_id: "abc123" }.to_json, headers: {})
444+
445+
client.trigger_broadcast(req).should eq({ "trigger_id" => "abc123" })
446+
end
447+
448+
it "sends with email list audience" do
449+
req = Customerio::TriggerBroadcastRequest.new(
450+
broadcast_id: 12,
451+
emails: ["a@example.com", "b@example.com"],
452+
email_add_duplicates: false,
453+
email_ignore_missing: true,
454+
)
455+
456+
stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
457+
.with(headers: request_headers, body: req.message)
458+
.to_return(status: 200, body: { trigger_id: "abc123" }.to_json, headers: {})
459+
460+
client.trigger_broadcast(req).should eq({ "trigger_id" => "abc123" })
461+
end
462+
463+
it "sends with id list audience" do
464+
req = Customerio::TriggerBroadcastRequest.new(
465+
broadcast_id: 12,
466+
ids: [1, 2, 3],
467+
id_ignore_missing: true,
468+
)
469+
470+
stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
471+
.with(headers: request_headers, body: req.message)
472+
.to_return(status: 200, body: { trigger_id: "abc123" }.to_json, headers: {})
473+
474+
client.trigger_broadcast(req).should eq({ "trigger_id" => "abc123" })
475+
end
476+
477+
it "sends with data_file_url audience" do
478+
req = Customerio::TriggerBroadcastRequest.new(
479+
broadcast_id: 12,
480+
data_file_url: "https://example.com/data.json",
481+
)
482+
483+
stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
484+
.with(headers: request_headers, body: req.message)
485+
.to_return(status: 200, body: { trigger_id: "abc123" }.to_json, headers: {})
486+
487+
client.trigger_broadcast(req).should eq({ "trigger_id" => "abc123" })
488+
end
489+
490+
it "raises an error when broadcast_id is missing" do
491+
lambda {
492+
Customerio::TriggerBroadcastRequest.new(data: { headline: "Test" })
493+
}.should raise_error(ArgumentError, "broadcast_id is required")
494+
end
495+
496+
it "raises an error when broadcast_id is not an integer" do
497+
lambda {
498+
Customerio::TriggerBroadcastRequest.new(broadcast_id: "12")
499+
}.should raise_error(ArgumentError, "broadcast_id must be an integer")
500+
end
501+
502+
it "raises an error when multiple audience fields are provided" do
503+
lambda {
504+
Customerio::TriggerBroadcastRequest.new(
505+
broadcast_id: 12,
506+
emails: ["a@example.com"],
507+
ids: [1, 2],
508+
)
509+
}.should raise_error(ArgumentError, /only one of/)
510+
end
511+
512+
it "raises an error when request is not a TriggerBroadcastRequest" do
513+
lambda {
514+
client.trigger_broadcast("not a request")
515+
}.should raise_error(ArgumentError, /must be an instance of/)
516+
end
517+
518+
it "handles validation failures (400)" do
519+
req = Customerio::TriggerBroadcastRequest.new(
520+
broadcast_id: 12,
521+
emails: ["a@example.com"],
522+
)
523+
524+
err_json = { meta: { error: "example error" } }.to_json
525+
526+
stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
527+
.with(headers: request_headers, body: req.message)
528+
.to_return(status: 400, body: err_json, headers: {})
529+
530+
lambda { client.trigger_broadcast(req) }.should(
531+
raise_error(Customerio::InvalidResponse) { |error|
532+
error.message.should eq "example error"
533+
error.code.should eq "400"
534+
}
535+
)
536+
end
537+
538+
it "handles other failures (5xx)" do
539+
req = Customerio::TriggerBroadcastRequest.new(
540+
broadcast_id: 12,
541+
)
542+
543+
stub_request(:post, api_uri('/v1/campaigns/12/triggers'))
544+
.with(headers: request_headers, body: req.message)
545+
.to_return(status: 500, body: "Server unavailable", headers: {})
546+
547+
lambda { client.trigger_broadcast(req) }.should(
548+
raise_error(Customerio::InvalidResponse) { |error|
549+
error.message.should eq "Server unavailable"
550+
error.code.should eq "500"
551+
}
552+
)
553+
end
554+
end
432555
end

0 commit comments

Comments
 (0)