sendeez

Zero-dependency Ruby SDK for the Sendeez transactional email API.

require "sendeez"

sendeez = Sendeez::Client.new(ENV.fetch("SENDEEZ_API_KEY"))

email = sendeez.emails.send_email(
  {
    "from" => { "email" => "hello@example.com", "name" => "Example" },
    "to" => [{ "email" => "developer@example.com" }],
    "subject" => "Welcome",
    "text" => "Your account is ready."
  },
  idempotency_key: "welcome-user-123" # Optional. The SDK generates one when omitted.
)

puts "#{email['id']} #{email['status']}"

Note the input hash needs explicit { } braces at the call site: emails methods take keyword arguments too (idempotency_key:, timeout:), and Ruby 3+ only treats a bare trailing hash as positional when it's wrapped in braces — otherwise it tries (and fails) to parse it as keywords.

The send method is named send_email, not send — overriding Kernel#send with a method that also declares keyword parameters hits a real Ruby VM bug (argument count coming out as 0), not just a style concern.

Errors and agent actions

begin
  sendeez.emails.send_email(message)
rescue Sendeez::Error => e
  puts [e.code, e.param, e.request_id, e.action, e.retry_after].inspect
end

The SDK retries GET requests and mutations carrying an idempotency key after network errors, 429, or 5xx. It never automatically retries an unsafe mutation.

Every method accepts a timeout: (seconds). This lets applications and AI agents enforce their own execution deadline:

sendeez.emails.list(timeout: 5)

Testing

Pass transport: to substitute a fake transport, the same role fetch/ opener/transport injection plays in the Node, Python, and PHP SDKs:

fake_transport = lambda do |method, url, headers, body, timeout|
  { status: 200, headers: {}, body: "{}" }
end
client = Sendeez::Client.new("sendeez_example_secret", transport: fake_transport)