Postscale Ruby SDK

Official Ruby SDK for the Postscale email API.

Install

gem install postscale

Or add to your Gemfile:

gem 'postscale'

Requires Ruby 3.0 or newer.

Send Email

require 'postscale'

postscale = Postscale::Client.new(api_key: ENV.fetch('POSTSCALE_API_KEY'))

result = postscale.emails.send(
  from: 'hello@example.com',
  to: ['user@example.com'],
  subject: 'Welcome!',
  html_body: '<strong>It works.</strong>'
)

if result.error
  raise "#{result.error.code}: #{result.error.message}"
end

puts result.data['message_id']

The SDK keeps Postscale API-native field names. Use html_body, text_body, template_id, and unsubscribe_scope; the MVP SDK does not add html or text aliases.

Configuration

postscale = Postscale::Client.new(
  api_key: ENV['POSTSCALE_API_KEY'],
  base_url: 'https://api.postscale.io',
  timeout: 30,
  max_retries: 3,
  headers: {}
)
Option Description Default
api_key Your Postscale API key. Falls back to POSTSCALE_API_KEY. Required
base_url API base URL https://api.postscale.io
timeout Request timeout in seconds 30
max_retries Retries for idempotent GET, HEAD, and OPTIONS requests 3
headers Additional default request headers {}

If no base URL is passed, the SDK reads POSTSCALE_BASE_URL and then falls back to https://api.postscale.io.

Results and Errors

Every API method returns a result object:

result = postscale.domains.list(limit: 10)

if result.error
  warn "#{result.error.code}: #{result.error.message}"
  warn result.error.request_id
else
  puts result.data
end

API errors are returned as Postscale::APIError objects inside result.error. Configuration and local validation errors are raised before a request is sent.

Attachments

attachment = Postscale.attachment_from_file('invoice.pdf', 'application/pdf')

result = postscale.emails.send(
  from: 'billing@example.com',
  to: ['customer@example.com'],
  subject: 'Invoice',
  text_body: 'Attached.',
  attachments: [attachment]
)

Attachment helpers return API-native hashes with filename, content, and content_type. Client-side checks enforce the documented limits: 10 attachments, 25 MB per attachment, and 50 MB total decoded attachment payload.

Webhook Verification

result = Postscale.verify_webhook_signature(
  raw_body,
  request.get_header('HTTP_X_POSTSCALE_SIGNATURE'),
  [current_secret, previous_secret]
)

halt 401 unless result.valid?

The verifier requires the Postscale signature format: t=<unix_seconds>,v1=<hex_hmac_sha256>, signed over <t>.<raw_body>. It supports multiple v1= signatures and multiple candidate secrets for rotation windows.

API Reference

Emails

postscale.emails.send(...)
postscale.emails.send_batch(emails: [...])
postscale.emails.list(...)
postscale.emails.get(email_id)
postscale.emails.list_events(email_id)

Domains

postscale.domains.create(domain: 'example.com')
postscale.domains.list
postscale.domains.get(domain_id)
postscale.domains.update(domain_id, active: true)
postscale.domains.verify(domain_id)
postscale.domains.dns(domain_id)
postscale.domains.delete(domain_id)

DKIM

postscale.dkim.generate(domain_id, selector: 's1')
postscale.dkim.list(domain_id)
postscale.dkim.rotate(domain_id, old_selector: 's1', new_selector: 's2')
postscale.dkim.deactivate(domain_id, 's1')

Inbound

postscale.inbound.list(...)
postscale.inbound.get(email_id)
postscale.inbound.download_attachment(email_id, attachment_id)

Suppressions

postscale.suppressions.list
postscale.suppressions.add(email: 'user@example.com', reason: 'hard_bounce')
postscale.suppressions.check('user@example.com')
postscale.suppressions.remove('user@example.com')
postscale.suppressions.import_preview(...)
postscale.suppressions.import_job(job_id)
postscale.suppressions.commit_import(job_id)

Templates

postscale.templates.create(...)
postscale.templates.list
postscale.templates.get(template_id)
postscale.templates.update(template_id, ...)
postscale.templates.preview(template_id, ...)
postscale.templates.delete(template_id)

Webhooks

postscale.webhooks.create(url: 'https://example.com/webhooks', events: ['email.delivered'])
postscale.webhooks.list
postscale.webhooks.deliveries
postscale.webhooks.endpoint_deliveries(webhook_id)
postscale.webhooks.rotate_secret(webhook_id)
postscale.webhooks.delete(webhook_id)
postscale.webhooks.verify_signature(raw_body, signature_header, secrets)

Warming, Stats, Usage, and Trust

postscale.warming.get_status(domain_id)
postscale.warming.history(domain_id)
postscale.warming.start(domain_id)
postscale.warming.pause(domain_id)
postscale.warming.resume(domain_id)

postscale.stats.aggregate(domain_id)
postscale.stats.daily(domain_id)
postscale.stats.hourly(domain_id)
postscale.stats.isp(domain_id)

postscale.usage.summary
postscale.trust.get_review_request
postscale.trust.create_review_request(...)

Testing

rake test

License

MIT