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:



208
209
210
211
212
213
# File 'lib/sendly/messages.rb', line 208

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



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/sendly/messages.rb', line 114

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

#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:



92
93
94
95
96
97
98
99
100
# File 'lib/sendly/messages.rb', line 92

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:



265
266
267
268
269
270
# File 'lib/sendly/messages.rb', line 265

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:



190
191
192
193
194
195
# File 'lib/sendly/messages.rb', line 190

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:



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/sendly/messages.rb', line 70

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



282
283
284
285
286
287
288
289
290
# File 'lib/sendly/messages.rb', line 282

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



170
171
172
173
174
175
176
177
178
# File 'lib/sendly/messages.rb', line 170

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:



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/sendly/messages.rb', line 310

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:



147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/sendly/messages.rb', line 147

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:, 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)

  • 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:



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

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

  body = { to: to, text: text }
  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:



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/sendly/messages.rb', line 234

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