diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 674850e..cfcb690 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,3 +1,7 @@ +## Customerio 5.1.0 - May 5, 2023 +### Added +- Added `send_push` to `APIClient` and `SendPushRequest` to support sending transactional push notifications. + ## Customerio 4.3.1 - January 5, 2023 ### Added - Added the `disable_css_preprocessing` and `language` optional fields to send request diff --git a/README.md b/README.md index 0a15fb2..b438fda 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,9 @@ $customerio.unsuppress(5) ### Send Transactional Messages -To use the Customer.io [Transactional API](https://customer.io/docs/transactional-api), create an instance of the API client using an [app key](https://customer.io/docs/managing-credentials#app-api-keys). +To use the Customer.io [Transactional API](https://customer.io/docs/transactional-api), create an instance of the API client using an [app key](https://customer.io/docs/managing-credentials#app-api-keys) and create a request object of your message type. + +#### Email Create a new `SendEmailRequest` object containing: @@ -262,6 +264,44 @@ rescue Customerio::InvalidResponse => e end ``` +#### Push + +Create a new `SendPushRequest` object containing: + +* `transactional_message_id`: the ID or trigger name of the transactional message you want to send. +* an `identifiers` object containing the `id` or `email` of your recipient. If the profile does not exist, Customer.io creates it. + +Use `send_push` referencing your request to send a transactional message. [Learn more about transactional messages and `SendPushRequest` properties](https://customer.io/docs/transactional-api). + + +```ruby +require "customerio" + +client = Customerio::APIClient.new("your API key", region: Customerio::Regions::US) + +request = Customerio::SendPushRequest.new( + transactional_message_id: "3", + message_data: { + name: "Person", + items: { + name: "shoes", + price: "59.99", + }, + products: [], + }, + identifiers: { + id: "2", + }, +) + +begin + response = client.send_push(request) + puts response +rescue Customerio::InvalidResponse => e + puts e.code, e.message +end +``` + ## Contributing 1. Fork it diff --git a/lib/customerio.rb b/lib/customerio.rb index 95f33f6..e824c5b 100644 --- a/lib/customerio.rb +++ b/lib/customerio.rb @@ -5,6 +5,7 @@ module Customerio require "customerio/base_client" require "customerio/client" require "customerio/requests/send_email_request" + require "customerio/requests/send_push_request" require "customerio/api" require "customerio/param_encoder" end diff --git a/lib/customerio/api.rb b/lib/customerio/api.rb index 2530f76..97394b1 100644 --- a/lib/customerio/api.rb +++ b/lib/customerio/api.rb @@ -26,10 +26,29 @@ def send_email(req) end end + def send_push(req) + raise "request must be an instance of Customerio::SendPushRequest" unless req.is_a?(Customerio::SendPushRequest) + response = @client.request(:post, send_push_path, req.message) + + case response + when Net::HTTPSuccess then + JSON.parse(response.body) + when Net::HTTPBadRequest then + json = JSON.parse(response.body) + raise Customerio::InvalidResponse.new(response.code, json['meta']['error'], response) + else + raise InvalidResponse.new(response.code, response.body) + end + end + private def send_email_path "/v1/send/email" end + + def send_push_path + "/v1/send/push" + end end end diff --git a/lib/customerio/requests/send_push_request.rb b/lib/customerio/requests/send_push_request.rb new file mode 100644 index 0000000..1224c23 --- /dev/null +++ b/lib/customerio/requests/send_push_request.rb @@ -0,0 +1,36 @@ +module Customerio + class SendPushRequest + attr_reader :message + + def initialize(opts) + @message = opts.delete_if { |field| invalid_field?(field) } + @message[:custom_device] = opts[:device] if opts[:device] + end + + private + + REQUIRED_FIELDS = %i(transactional_message_id identifiers) + + OPTIONAL_FIELDS = %i( + to + title + message + disable_message_retention + send_to_unsubscribed + queue_draft + message_data + send_at + language + image_url + link + sound + custom_data + device + custom_device + ) + + def invalid_field?(field) + !REQUIRED_FIELDS.include?(field) && !OPTIONAL_FIELDS.include?(field) + end + end +end diff --git a/lib/customerio/version.rb b/lib/customerio/version.rb index becdd6e..d5e7a94 100644 --- a/lib/customerio/version.rb +++ b/lib/customerio/version.rb @@ -1,3 +1,3 @@ module Customerio - VERSION = "5.0.0" + VERSION = "5.1.0" end diff --git a/spec/api_client_spec.rb b/spec/api_client_spec.rb index a7a0db6..fac44ab 100644 --- a/spec/api_client_spec.rb +++ b/spec/api_client_spec.rb @@ -6,7 +6,7 @@ describe Customerio::APIClient do let(:app_key) { "appkey" } - let(:client) { Customerio::APIClient.new(app_key) } + let(:client) { Customerio::APIClient.new(app_key) } let(:response) { double("Response", code: 200) } def api_uri(path) @@ -169,4 +169,87 @@ def json(data) req.message[:attachments].should eq({ "test" => Base64.strict_encode64("test-content") }) end end + + describe "#send_push" do + it "sends a POST request to the /api/send/push path" do + req = Customerio::SendPushRequest.new( + identifiers: { + id: 'c1', + }, + transactional_message_id: 1, + ) + + stub_request(:post, api_uri('/v1/send/push')) + .with(headers: request_headers, body: req.message) + .to_return(status: 200, body: { delivery_id: 1 }.to_json, headers: {}) + + client.send_push(req).should eq({ "delivery_id" => 1 }) + end + + it "handles validation failures (400)" do + req = Customerio::SendPushRequest.new( + identifiers: { + id: 'c1', + }, + transactional_message_id: 1, + ) + + err_json = { meta: { error: "example error" } }.to_json + + stub_request(:post, api_uri('/v1/send/push')) + .with(headers: request_headers, body: req.message) + .to_return(status: 400, body: err_json, headers: {}) + + lambda { client.send_push(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::SendPushRequest.new( + identifiers: { + id: 'c1', + }, + transactional_message_id: 1, + ) + + stub_request(:post, api_uri('/v1/send/push')) + .with(headers: request_headers, body: req.message) + .to_return(status: 500, body: "Server unavailable", headers: {}) + + lambda { client.send_push(req) }.should( + raise_error(Customerio::InvalidResponse) { |error| + error.message.should eq "Server unavailable" + error.code.should eq "500" + } + ) + end + + it "sets custom_device correctly if device present in req" do + req = Customerio::SendPushRequest.new( + identifiers: { + id: 'c1', + }, + transactional_message_id: 1, + device: { + platform: 'ios', + token: 'sample-token', + } + ) + + req.message[:custom_device].should eq({ + platform: 'ios', + token: 'sample-token', + }) + + stub_request(:post, api_uri('/v1/send/push')) + .with(headers: request_headers, body: req.message) + .to_return(status: 200, body: { delivery_id: 2 }.to_json, headers: {}) + + client.send_push(req).should eq({ "delivery_id" => 2 }) + end + end end