From 15dafc7727c5f55f6b724f5fa0bef02035e0b18d Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Thu, 16 Jul 2026 23:25:06 -0400 Subject: [PATCH 1/3] Add send_whatsapp transactional method Adds a send_whatsapp method for POST /v1/send/whatsapp, mirroring the existing send_sms surface (from/to/tracked + standard transactional fields). --- README.md | 37 +++++++++++ lib/customerio.rb | 1 + lib/customerio/api.rb | 10 +++ .../requests/send_whatsapp_request.rb | 40 ++++++++++++ spec/api_client_spec.rb | 62 +++++++++++++++++++ 5 files changed, 150 insertions(+) create mode 100644 lib/customerio/requests/send_whatsapp_request.rb diff --git a/README.md b/README.md index fb72d53..4173963 100644 --- a/README.md +++ b/README.md @@ -439,6 +439,43 @@ 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` and `from` are WhatsApp numbers in E.164 format. `from` is optional when the referenced `transactional_message_id` already defines it. + +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", + from: "+15559876543", + 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..6c9441f --- /dev/null +++ b/lib/customerio/requests/send_whatsapp_request.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "base64" + +module Customerio + class SendWhatsAppRequest + REQUIRED_FIELDS = %i[identifiers transactional_message_id].freeze + + OPTIONAL_FIELDS = %i[ + message_data + from + 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( From 8f8bdc9dafde531f5ce1bb45b730a5a538eef4e8 Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 24 Jul 2026 14:34:39 -0400 Subject: [PATCH 2/3] drop from --- lib/customerio/requests/send_whatsapp_request.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/customerio/requests/send_whatsapp_request.rb b/lib/customerio/requests/send_whatsapp_request.rb index 6c9441f..ede2afd 100644 --- a/lib/customerio/requests/send_whatsapp_request.rb +++ b/lib/customerio/requests/send_whatsapp_request.rb @@ -8,7 +8,6 @@ class SendWhatsAppRequest OPTIONAL_FIELDS = %i[ message_data - from to disable_message_retention send_to_unsubscribed From 25af779cfb862ae60048b4741b6f40ad88b4c14f Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 24 Jul 2026 16:24:38 -0400 Subject: [PATCH 3/3] Remove from from WhatsApp README example --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 4173963..3f43173 100644 --- a/README.md +++ b/README.md @@ -446,7 +446,7 @@ 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` and `from` are WhatsApp numbers in E.164 format. `from` is optional when the referenced `transactional_message_id` already defines 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). @@ -459,7 +459,6 @@ client = Customerio::APIClient.new("your API key", region: Customerio::Regions:: request = Customerio::SendWhatsAppRequest.new( transactional_message_id: "3", to: "+15551234567", - from: "+15559876543", message_data: { name: "Person", },