Class: Sendly::Messages
- Inherits:
-
Object
- Object
- Sendly::Messages
- Defined in:
- lib/sendly/messages.rb
Overview
Messages resource for sending and managing SMS
Instance Attribute Summary collapse
-
#client ⇒ Sendly::Client
readonly
The API client.
Instance Method Summary collapse
-
#cancel_scheduled(id) ⇒ Hash
Cancel a scheduled message.
-
#each(status: nil, to: nil, batch_size: 100) {|Message| ... } ⇒ Enumerator
Iterate over all messages with automatic pagination.
-
#enhance(text: nil, message_type: nil) ⇒ Sendly::EnhancedMessage
AI-enhance a draft message for clarity, compliance, and send-readiness.
-
#get(id) ⇒ Sendly::Message
Get a message by ID.
-
#get_batch(batch_id) ⇒ Hash
Get batch status by ID.
-
#get_scheduled(id) ⇒ Hash
Get a scheduled message by ID.
-
#initialize(client) ⇒ Messages
constructor
A new instance of Messages.
-
#list(limit: 20, offset: 0, status: nil, to: nil) ⇒ Sendly::MessageList
List messages.
-
#list_batches(limit: 20, offset: 0, status: nil) ⇒ Hash
List batches.
-
#list_scheduled(limit: 20, offset: 0, status: nil) ⇒ Hash
List scheduled messages.
-
#preview_batch(messages:, from: nil, message_type: nil) ⇒ Hash
Preview a batch without sending (dry run).
-
#schedule(to:, text:, scheduled_at:, from: nil, message_type: nil, metadata: nil, idempotency_key: nil) ⇒ Hash
Schedule an SMS message for future delivery.
-
#send(to:, text: nil, from: nil, message_type: nil, metadata: nil, media_urls: nil, channel: nil, template: nil, agent_id: nil, card: nil, suggestions: nil, fallback_to_sms: nil, idempotency_key: nil) ⇒ Sendly::Message, ...
Send an SMS, WhatsApp, or RCS message.
-
#send_batch(messages:, from: nil, message_type: nil, metadata: nil, idempotency_key: nil) ⇒ Hash
Send multiple SMS messages in a batch.
-
#send_group(to:, text: nil, from: nil, media_urls: nil, message_type: nil, idempotency_key: nil) ⇒ Sendly::GroupMessage
Send a group MMS to 2-8 recipients (US/Canada only).
Constructor Details
#initialize(client) ⇒ Messages
Returns a new instance of Messages.
9 10 11 |
# File 'lib/sendly/messages.rb', line 9 def initialize(client) @client = client end |
Instance Attribute Details
#client ⇒ Sendly::Client (readonly)
Returns The API client.
7 8 9 |
# File 'lib/sendly/messages.rb', line 7 def client @client end |
Instance Method Details
#cancel_scheduled(id) ⇒ Hash
Cancel a scheduled message
434 435 436 437 438 439 |
# File 'lib/sendly/messages.rb', line 434 def cancel_scheduled(id) raise ValidationError, "Scheduled message ID is required" if id.nil? || id.empty? encoded_id = URI.encode_www_form_component(id) client.delete("/messages/scheduled/#{encoded_id}") end |
#each(status: nil, to: nil, batch_size: 100) {|Message| ... } ⇒ Enumerator
Iterate over all messages with automatic pagination
338 339 340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/sendly/messages.rb', line 338 def each(status: nil, to: nil, batch_size: 100, &block) return enum_for(:each, status: status, to: to, batch_size: batch_size) unless block_given? offset = 0 loop do page = list(limit: batch_size, offset: offset, status: status, to: to) page.each(&block) break unless page.has_more offset += batch_size end end |
#enhance(text: nil, message_type: nil) ⇒ Sendly::EnhancedMessage
AI-enhance a draft message for clarity, compliance, and send-readiness
Rewrites the supplied text into a single, polished SMS segment (<=160
chars) and returns a short explanation of what changed. Pass message_type
to steer the rewrite (e.g. "marketing" vs "transactional"); with no text
it generates a suitable message for that type instead. At least one of
text or message_type is required. Requires the ai_classification
feature. When AI enhancement is unavailable, the response falls back to the
original text with an empty explanation.
264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/sendly/messages.rb', line 264 def enhance(text: nil, message_type: nil) if (text.nil? || text.to_s.empty?) && (.nil? || .to_s.empty?) raise ValidationError, "Provide 'text' or 'message_type'" end body = {} body[:text] = text unless text.nil? body[:messageType] = if response = client.post("/ai/enhance", body) EnhancedMessage.new(response) end |
#get(id) ⇒ Sendly::Message
Get a message by ID
316 317 318 319 320 321 322 323 324 |
# File 'lib/sendly/messages.rb', line 316 def get(id) raise ValidationError, "Message ID is required" if id.nil? || id.empty? # URL encode the ID to prevent path injection encoded_id = URI.encode_www_form_component(id) response = client.get("/messages/#{encoded_id}") # API returns message directly at top level Message.new(response) end |
#get_batch(batch_id) ⇒ Hash
Get batch status by ID
497 498 499 500 501 502 |
# File 'lib/sendly/messages.rb', line 497 def get_batch(batch_id) raise ValidationError, "Batch ID is required" if batch_id.nil? || batch_id.empty? encoded_id = URI.encode_www_form_component(batch_id) client.get("/messages/batch/#{encoded_id}") end |
#get_scheduled(id) ⇒ Hash
Get a scheduled message by ID
416 417 418 419 420 421 |
# File 'lib/sendly/messages.rb', line 416 def get_scheduled(id) raise ValidationError, "Scheduled message ID is required" if id.nil? || id.empty? encoded_id = URI.encode_www_form_component(id) client.get("/messages/scheduled/#{encoded_id}") end |
#list(limit: 20, offset: 0, status: nil, to: nil) ⇒ Sendly::MessageList
List messages
294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/sendly/messages.rb', line 294 def list(limit: 20, offset: 0, status: nil, to: nil) params = { limit: [limit, 100].min, offset: offset } params[:status] = status if status params[:to] = to if to response = client.get("/messages", params.compact) MessageList.new(response) end |
#list_batches(limit: 20, offset: 0, status: nil) ⇒ Hash
List batches
514 515 516 517 518 519 520 521 522 |
# File 'lib/sendly/messages.rb', line 514 def list_batches(limit: 20, offset: 0, status: nil) params = { limit: [limit, 100].min, offset: offset } params[:status] = status if status client.get("/messages/batches", params.compact) end |
#list_scheduled(limit: 20, offset: 0, status: nil) ⇒ Hash
List scheduled messages
396 397 398 399 400 401 402 403 404 |
# File 'lib/sendly/messages.rb', line 396 def list_scheduled(limit: 20, offset: 0, status: nil) params = { limit: [limit, 100].min, offset: offset } params[:status] = status if status client.get("/messages/scheduled", params.compact) end |
#preview_batch(messages:, from: nil, message_type: nil) ⇒ Hash
Preview a batch without sending (dry run)
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 |
# File 'lib/sendly/messages.rb', line 542 def preview_batch(messages:, from: nil, message_type: nil) raise ValidationError, "Messages array is required" if .nil? || .empty? .each_with_index do |msg, i| raise ValidationError, "Message at index #{i} missing 'to'" unless msg[:to] || msg["to"] raise ValidationError, "Message at index #{i} missing 'text'" unless msg[:text] || msg["text"] to = msg[:to] || msg["to"] text = msg[:text] || msg["text"] validate_phone!(to) validate_text!(text) end body = { messages: } body[:from] = from if from body[:messageType] = if client.post("/messages/batch/preview", body) end |
#schedule(to:, text:, scheduled_at:, from: nil, message_type: nil, metadata: nil, idempotency_key: nil) ⇒ Hash
Schedule an SMS message for future delivery
373 374 375 376 377 378 379 380 381 382 383 384 |
# File 'lib/sendly/messages.rb', line 373 def schedule(to:, text:, scheduled_at:, from: nil, message_type: nil, metadata: nil, idempotency_key: nil) validate_phone!(to) validate_text!(text) raise ValidationError, "scheduled_at is required" if scheduled_at.nil? || scheduled_at.empty? body = { to: to, text: text, scheduledAt: scheduled_at } body[:from] = from if from body[:messageType] = if body[:metadata] = if client.post("/messages/schedule", body, idempotency_key: idempotency_key) end |
#send(to:, text: nil, from: nil, message_type: nil, metadata: nil, media_urls: nil, channel: nil, template: nil, agent_id: nil, card: nil, suggestions: nil, fallback_to_sms: nil, idempotency_key: nil) ⇒ Sendly::Message, ...
Send an SMS, WhatsApp, or RCS message
Pass channel: "whatsapp" to send on WhatsApp. WhatsApp sends require
a live API key and a from number with an active WhatsApp connection
(see client.whatsapp.signup). Provide exactly one of text
(free-form, max 4096 bytes), media_urls (a single attachment;
optional text becomes its caption, max 1024 bytes), or template
(an approved template). Free-form text and media only deliver inside
an open 24-hour customer-service window — outside it the API responds
422 whatsapp_window_closed; send a template instead (check with
client.whatsapp.window).
Pass channel: "rcs" to send on RCS. RCS sends require a live API key
and a sendable RCS agent on your workspace (see client.rcs.agents).
Provide exactly one of text (free-form, optionally with
suggestions — suggested replies and URL actions) or card (a rich
card). agent_id picks the sending agent when your workspace has more
than one. Recipients that can't receive RCS get text delivered as
plain SMS (billed as SMS) unless fallback_to_sms is false — the
returned RcsMessage discloses which channel delivered.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/sendly/messages.rb', line 140 def send(to:, text: nil, from: nil, message_type: nil, metadata: nil, media_urls: nil, channel: nil, template: nil, agent_id: nil, card: nil, suggestions: nil, fallback_to_sms: nil, idempotency_key: nil) validate_phone!(to) if channel.to_s == "rcs" has_text = !(text.nil? || text.to_s.empty?) has_card = !card.nil? raise ValidationError, "Provide exactly one of 'text' or 'card'" if has_text == has_card body = { channel: "rcs", to: to } body[:agentId] = agent_id if agent_id body[:text] = text if has_text body[:card] = card if has_card body[:suggestions] = suggestions if suggestions body[:fallbackToSms] = fallback_to_sms unless fallback_to_sms.nil? body[:metadata] = if response = client.post("/messages", body, idempotency_key: idempotency_key) return RcsMessage.new(response) end if channel.to_s == "whatsapp" validate_phone!(from) has_media = media_urls.is_a?(Array) && !media_urls.empty? if (text.nil? || text.empty?) && !has_media && template.nil? raise ValidationError, "Provide 'text', 'media_urls', or 'template'" end body = { channel: "whatsapp", to: to, from: from } body[:text] = text unless text.nil? body[:mediaUrls] = media_urls if has_media body[:template] = template if template body[:metadata] = if response = client.post("/messages", body, idempotency_key: idempotency_key) return WhatsAppMessage.new(response) end validate_text!(text) body = { to: to, text: text } body[:from] = from if from body[:messageType] = if body[:metadata] = if body[:mediaUrls] = media_urls if media_urls response = client.post("/messages", body, idempotency_key: idempotency_key) # API returns message directly at top level Message.new(response) end |
#send_batch(messages:, from: nil, message_type: nil, metadata: nil, idempotency_key: nil) ⇒ Hash
Send multiple SMS messages in a batch
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 |
# File 'lib/sendly/messages.rb', line 462 def send_batch(messages:, from: nil, message_type: nil, metadata: nil, idempotency_key: nil) raise ValidationError, "Messages array is required" if .nil? || .empty? .each_with_index do |msg, i| raise ValidationError, "Message at index #{i} missing 'to'" unless msg[:to] || msg["to"] raise ValidationError, "Message at index #{i} missing 'text'" unless msg[:text] || msg["text"] to = msg[:to] || msg["to"] text = msg[:text] || msg["text"] validate_phone!(to) validate_text!(text) end body = { messages: } body[:from] = from if from body[:messageType] = if body[:metadata] = if # The batch endpoint dedupes header-less retries server-side by hashing # the request content; an auto-generated key would bypass that net for # identical cross-process re-runs, so only caller-supplied keys are sent. client.post("/messages/batch", body, idempotency_key: idempotency_key, auto_idempotency_key: false) end |
#send_group(to:, text: nil, from: nil, media_urls: nil, message_type: nil, idempotency_key: nil) ⇒ Sendly::GroupMessage
Send a group MMS to 2-8 recipients (US/Canada only)
Creates a multi-party MMS conversation: every recipient sees the others,
and replies fan out to all participants. Group messaging is an A2P 10DLC
capability — the sending number must be an MMS-enabled, 10DLC-registered
number you own. Omit from to use your workspace's default sender.
Requires the group_mms feature (and enable_mms when sending media).
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/sendly/messages.rb', line 219 def send_group(to:, text: nil, from: nil, media_urls: nil, message_type: nil, idempotency_key: nil) unless to.is_a?(Array) && to.length >= 2 raise ValidationError, "Group messaging requires at least 2 recipients in 'to'" end raise ValidationError, "Group messaging supports at most 8 recipients" if to.length > 8 to.each { |recipient| validate_phone!(recipient) } has_media = media_urls.is_a?(Array) && !media_urls.empty? raise ValidationError, "Provide 'text' or 'media_urls'" if (text.nil? || text.empty?) && !has_media body = { to: to } body[:text] = text if text && !text.empty? body[:from] = from if from body[:mediaUrls] = media_urls if has_media body[:messageType] = if response = client.post("/messages/group", body, idempotency_key: idempotency_key) GroupMessage.new(response) end |