Project

postscale

0.0
The project is in a healthy, maintained state
Send transactional emails, manage domains, DKIM, templates, webhooks, and more with the Postscale API.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

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