Class: Sendly::Messages

Inherits:
Object
  • Object
show all
Defined in:
lib/sendly/messages.rb

Overview

Messages resource for sending and managing SMS

Instance Attribute Summary collapse

Instance Method Summary collapse

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

#clientSendly::Client (readonly)

Returns The API client.

Returns:



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

Examples:

result = client.messages.cancel_scheduled("sched_abc123")
puts "Refunded #{result['creditsRefunded']} credits"

Parameters:

  • id (String)

    Scheduled message ID

Returns:

  • (Hash)

    The cancelled message with refund details

Raises:



293
294
295
296
297
298
# File 'lib/sendly/messages.rb', line 293

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

Examples:

client.messages.each do |message|
  puts "#{message.id}: #{message.to}"
end

Parameters:

  • status (String) (defaults to: nil)

    Filter by status

  • to (String) (defaults to: nil)

    Filter by recipient

  • batch_size (Integer) (defaults to: 100)

    Number of messages per request

Yields:

Returns:

  • (Enumerator)

    If no block given



199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/sendly/messages.rb', line 199

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.

Examples:

result = client.messages.enhance(
  text: "hey come check out our sale this weekend",
  message_type: "marketing"
)
puts result.enhanced     # polished, <=160-char rewrite
puts result.explanation  # what changed and why

Parameters:

  • text (String) (defaults to: nil)

    Draft message text to rewrite (optional if message_type given)

  • message_type (String) (defaults to: nil)

    Message-type hint, e.g. "marketing" or "transactional"

Returns:

Raises:



125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/sendly/messages.rb', line 125

def enhance(text: nil, message_type: nil)
  if (text.nil? || text.to_s.empty?) && (message_type.nil? || message_type.to_s.empty?)
    raise ValidationError, "Provide 'text' or 'message_type'"
  end

  body = {}
  body[:text] = text unless text.nil?
  body[:messageType] = message_type if message_type

  response = client.post("/ai/enhance", body)
  EnhancedMessage.new(response)
end

#get(id) ⇒ Sendly::Message

Get a message by ID

Examples:

message = client.messages.get("msg_abc123")
puts message.status

Parameters:

  • id (String)

    Message ID

Returns:

Raises:



177
178
179
180
181
182
183
184
185
# File 'lib/sendly/messages.rb', line 177

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

Examples:

batch = client.messages.get_batch("batch_abc123")
puts "#{batch['sent']}/#{batch['total']} sent"

Parameters:

  • batch_id (String)

    Batch ID

Returns:

  • (Hash)

    Batch status and details

Raises:



350
351
352
353
354
355
# File 'lib/sendly/messages.rb', line 350

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

Examples:

scheduled = client.messages.get_scheduled("sched_abc123")
puts scheduled["status"]

Parameters:

  • id (String)

    Scheduled message ID

Returns:

  • (Hash)

    The scheduled message

Raises:



275
276
277
278
279
280
# File 'lib/sendly/messages.rb', line 275

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

Examples:

messages = client.messages.list(limit: 50)
messages.each { |m| puts m.to }

With filters

messages = client.messages.list(
  status: "delivered",
  to: "+15551234567"
)

Parameters:

  • limit (Integer) (defaults to: 20)

    Maximum messages to return (default: 20, max: 100)

  • offset (Integer) (defaults to: 0)

    Number of messages to skip

  • status (String) (defaults to: nil)

    Filter by status

  • to (String) (defaults to: nil)

    Filter by recipient

Returns:



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/sendly/messages.rb', line 155

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

Examples:

batches = client.messages.list_batches(limit: 10)
batches["data"].each { |b| puts "#{b['batchId']}: #{b['status']}" }

Parameters:

  • limit (Integer) (defaults to: 20)

    Maximum batches to return (default: 20, max: 100)

  • offset (Integer) (defaults to: 0)

    Number of batches to skip

  • status (String) (defaults to: nil)

    Filter by status (processing, completed, failed)

Returns:

  • (Hash)

    Paginated list of batches



367
368
369
370
371
372
373
374
375
# File 'lib/sendly/messages.rb', line 367

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

Examples:

scheduled = client.messages.list_scheduled(limit: 50)
scheduled["data"].each { |m| puts m["scheduledAt"] }

Parameters:

  • limit (Integer) (defaults to: 20)

    Maximum messages to return (default: 20, max: 100)

  • offset (Integer) (defaults to: 0)

    Number of messages to skip

  • status (String) (defaults to: nil)

    Filter by status (scheduled, sent, cancelled, failed)

Returns:

  • (Hash)

    Paginated list of scheduled messages



255
256
257
258
259
260
261
262
263
# File 'lib/sendly/messages.rb', line 255

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)

Examples:

preview = client.messages.preview_batch(
  messages: [
    { to: "+15551234567", text: "Hello Alice!" },
    { to: "+15559876543", text: "Hello Bob!" }
  ]
)
puts "Can send: #{preview['canSend']}"
puts "Credits needed: #{preview['creditsNeeded']}"

Parameters:

  • messages (Array<Hash>)

    Array of messages with :to and :text keys

  • from (String) (defaults to: nil)

    Sender ID or phone number (optional, applies to all)

  • message_type (String) (defaults to: nil)

    Message type: "marketing" (default) or "transactional"

Returns:

  • (Hash)

    Preview showing what would happen if batch was sent

Raises:



395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/sendly/messages.rb', line 395

def preview_batch(messages:, from: nil, message_type: nil)
  raise ValidationError, "Messages array is required" if messages.nil? || messages.empty?

  messages.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: messages }
  body[:from] = from if from
  body[:messageType] = message_type if message_type

  client.post("/messages/batch/preview", body)
end

#schedule(to:, text:, scheduled_at:, from: nil, message_type: nil, metadata: nil) ⇒ Hash

Schedule an SMS message for future delivery

Examples:

scheduled = client.messages.schedule(
  to: "+15551234567",
  text: "Reminder: Your appointment is tomorrow!",
  scheduled_at: "2025-01-20T10:00:00Z"
)
puts scheduled["id"]

Parameters:

  • to (String)

    Recipient phone number in E.164 format

  • text (String)

    Message content (max 1600 characters)

  • scheduled_at (String)

    ISO 8601 datetime for when to send

  • from (String) (defaults to: nil)

    Sender ID or phone number (optional)

  • message_type (String) (defaults to: nil)

    Message type: "marketing" (default) or "transactional"

  • metadata (Hash) (defaults to: nil)

    Custom JSON metadata to attach to the message (max 4KB)

Returns:

  • (Hash)

    The scheduled message

Raises:



232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/sendly/messages.rb', line 232

def schedule(to:, text:, scheduled_at:, from: nil, message_type: nil, metadata: 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] = message_type if message_type
  body[:metadata] =  if 

  client.post("/messages/schedule", body)
end

#send(to:, text:, from: nil, message_type: nil, metadata: nil, media_urls: nil) ⇒ Sendly::Message

Send an SMS message

Examples:

message = client.messages.send(
  to: "+15551234567",
  text: "Hello from Sendly!"
)
puts message.id
puts message.status

Transactional message (bypasses quiet hours)

message = client.messages.send(
  to: "+15551234567",
  text: "Your verification code is 123456",
  message_type: "transactional"
)

Parameters:

  • to (String)

    Recipient phone number in E.164 format

  • text (String)

    Message content (max 1600 characters)

  • from (String) (defaults to: nil)

    Sender ID or phone number (optional)

  • message_type (String) (defaults to: nil)

    Message type: "marketing" (default) or "transactional"

  • metadata (Hash) (defaults to: nil)

    Custom JSON metadata to attach to the message (max 4KB)

Returns:

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/sendly/messages.rb', line 40

def send(to:, text:, from: nil, message_type: nil, metadata: nil, media_urls: nil)
  validate_phone!(to)
  validate_text!(text)

  body = { to: to, text: text }
  body[:from] = from if from
  body[:messageType] = message_type if message_type
  body[:metadata] =  if 
  body[:mediaUrls] = media_urls if media_urls

  response = client.post("/messages", body)
  # API returns message directly at top level
  Message.new(response)
end

#send_batch(messages:, from: nil, message_type: nil, metadata: nil) ⇒ Hash

Send multiple SMS messages in a batch

Examples:

result = client.messages.send_batch(
  messages: [
    { to: "+15551234567", text: "Hello Alice!" },
    { to: "+15559876543", text: "Hello Bob!" }
  ]
)
puts "Batch #{result['batchId']}: #{result['queued']} queued"

Parameters:

  • messages (Array<Hash>)

    Array of messages with :to and :text keys

  • from (String) (defaults to: nil)

    Sender ID or phone number (optional, applies to all)

  • message_type (String) (defaults to: nil)

    Message type: "marketing" (default) or "transactional"

  • metadata (Hash) (defaults to: nil)

    Shared metadata for all messages (max 4KB). Each message can also have its own metadata hash which takes priority.

Returns:

  • (Hash)

    Batch response with batch_id and status

Raises:



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/sendly/messages.rb', line 319

def send_batch(messages:, from: nil, message_type: nil, metadata: nil)
  raise ValidationError, "Messages array is required" if messages.nil? || messages.empty?

  messages.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: messages }
  body[:from] = from if from
  body[:messageType] = message_type if message_type
  body[:metadata] =  if 

  client.post("/messages/batch", body)
end

#send_group(to:, text: nil, from: nil, media_urls: nil, message_type: 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).

Examples:

group = client.messages.send_group(
  to: ["+14155551234", "+14155555678"],
  text: "Hey team - quick sync at noon?"
)
puts group.id
puts group.group_message_id

Parameters:

  • to (Array<String>)

    2-8 recipient phone numbers in E.164 format (US/CA only)

  • text (String) (defaults to: nil)

    Message content (required unless media_urls is provided)

  • from (String) (defaults to: nil)

    Sender ID or phone number (optional)

  • media_urls (Array<String>) (defaults to: nil)

    Media URLs to attach (required unless text is provided)

  • message_type (String) (defaults to: nil)

    Message type: "transactional" (default) or "marketing"

Returns:

Raises:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/sendly/messages.rb', line 80

def send_group(to:, text: nil, from: nil, media_urls: nil, message_type: 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] = message_type if message_type

  response = client.post("/messages/group", body)
  GroupMessage.new(response)
end