diff --git a/README.md b/README.md index fb72d53..3f43173 100644 --- a/README.md +++ b/README.md @@ -439,6 +439,42 @@ rescue Customerio::InvalidResponse => e end ``` +#### WhatsApp + +Create a new `SendWhatsAppRequest` 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. + +`to` is the WhatsApp number in E.164 format. + +Use `send_whatsapp` referencing your request to send a transactional message. [Learn more about transactional messages and `SendWhatsAppRequest` properties](https://customer.io/docs/transactional-api). + + +```ruby +require "customerio" + +client = Customerio::APIClient.new("your API key", region: Customerio::Regions::US) + +request = Customerio::SendWhatsAppRequest.new( + transactional_message_id: "3", + to: "+15551234567", + message_data: { + name: "Person", + }, + identifiers: { + id: "2", + }, +) + +begin + response = client.send_whatsapp(request) + puts response +rescue Customerio::InvalidResponse => e + puts e.code, e.message +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. diff --git a/lib/customerio.rb b/lib/customerio.rb index 641113b..98bed86 100644 --- a/lib/customerio.rb +++ b/lib/customerio.rb @@ -9,6 +9,7 @@ module Customerio require "customerio/requests/send_email_request" require "customerio/requests/send_push_request" require "customerio/requests/send_sms_request" + require "customerio/requests/send_whatsapp_request" require "customerio/requests/send_inbox_message_request" require "customerio/requests/send_in_app_request" require "customerio/requests/trigger_broadcast_request" diff --git a/lib/customerio/api.rb b/lib/customerio/api.rb index 54fa007..7c373b7 100644 --- a/lib/customerio/api.rb +++ b/lib/customerio/api.rb @@ -34,6 +34,12 @@ def send_sms(req) deliver(send_sms_path, req.message) end + def send_whatsapp(req) + validate_request!(req, SendWhatsAppRequest) + + deliver(send_whatsapp_path, req.message) + end + def send_inbox_message(req) validate_request!(req, SendInboxMessageRequest) @@ -86,6 +92,10 @@ def send_sms_path "/v1/send/sms" end + def send_whatsapp_path + "/v1/send/whatsapp" + end + def send_inbox_message_path "/v1/send/inbox_message" end diff --git a/lib/customerio/requests/send_whatsapp_request.rb b/lib/customerio/requests/send_whatsapp_request.rb new file mode 100644 index 0000000..ede2afd --- /dev/null +++ b/lib/customerio/requests/send_whatsapp_request.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "base64" + +module Customerio + class SendWhatsAppRequest + REQUIRED_FIELDS = %i[identifiers transactional_message_id].freeze + + OPTIONAL_FIELDS = %i[ + message_data + to + disable_message_retention + send_to_unsubscribed + tracked + queue_draft + send_at + language + ].freeze + + attr_reader :message + + def initialize(opts) + @message = opts.select { |field, _value| valid_field?(field) } + @message[:attachments] ||= {} + end + + def attach(name, data, encode: true) + raise ArgumentError, "attachment #{name} already exists" if @message[:attachments].key?(name) + + @message[:attachments][name] = encode ? Base64.strict_encode64(data) : data + end + + private + + def valid_field?(field) + REQUIRED_FIELDS.include?(field) || OPTIONAL_FIELDS.include?(field) + end + end +end diff --git a/spec/api_client_spec.rb b/spec/api_client_spec.rb index fb098b0..a961666 100644 --- a/spec/api_client_spec.rb +++ b/spec/api_client_spec.rb @@ -312,6 +312,68 @@ def json(data) end end + describe "#send_whatsapp" do + it "sends a POST request to the /api/send/whatsapp path" do + req = Customerio::SendWhatsAppRequest.new( + identifiers: { + id: 'c1', + }, + transactional_message_id: 1, + to: '+15551234567', + from: '+15559876543', + tracked: true, + ) + + stub_request(:post, api_uri('/v1/send/whatsapp')) + .with(headers: request_headers, body: req.message) + .to_return(status: 200, body: { delivery_id: 1 }.to_json, headers: {}) + + expect(client.send_whatsapp(req)).to eq({ "delivery_id" => 1 }) + end + + it "handles validation failures (400)" do + req = Customerio::SendWhatsAppRequest.new( + identifiers: { + id: 'c1', + }, + transactional_message_id: 1, + ) + + err_json = { meta: { error: "example error" } }.to_json + + stub_request(:post, api_uri('/v1/send/whatsapp')) + .with(headers: request_headers, body: req.message) + .to_return(status: 400, body: err_json, headers: {}) + + expect { client.send_whatsapp(req) }.to( + raise_error(Customerio::InvalidResponse) { |error| + expect(error.message).to eq("example error") + expect(error.code).to eq("400") + } + ) + end + + it "handles other failures (5xx)" do + req = Customerio::SendWhatsAppRequest.new( + identifiers: { + id: 'c1', + }, + transactional_message_id: 1, + ) + + stub_request(:post, api_uri('/v1/send/whatsapp')) + .with(headers: request_headers, body: req.message) + .to_return(status: 500, body: "Server unavailable", headers: {}) + + expect { client.send_whatsapp(req) }.to( + raise_error(Customerio::InvalidResponse) { |error| + expect(error.message).to eq("Server unavailable") + expect(error.code).to eq("500") + } + ) + end + end + describe "#send_inbox_message" do it "sends a POST request to the /api/send/inbox_message path" do req = Customerio::SendInboxMessageRequest.new(