Official Ruby client for the CycleTask Mail Forwarding API.
Manage domains, aliases, email forwarding rules, and view delivery stats — all from idiomatic Ruby.
Add to your Gemfile:
gem 'cycletask'Then run:
bundle installOr install directly:
gem install cycletaskrequire 'cycletask'
client = CycleTask::Client.new(api_key: 'ct_live_your_api_key')
# List all domains
domains = client.domains.list
domains.each { |d| puts "#{d.domain} — #{d.status}" }
# Create an alias
a = client.aliases.create(
alias: 'hello',
domain_id: domains.first.id,
destinations: ['me@gmail.com']
)
puts "Created: #{a.alias_address}@#{a.domain}"client = CycleTask::Client.new(
api_key: 'ct_live_your_api_key', # Required
base_url: 'https://api.task-cycle.com/api', # Default
timeout: 30, # Seconds, default: 30
max_retries: 3 # Retries on 429/5xx, default: 3
)# List all domains
domains = client.domains.list
# => [#<CycleTask::Types::Domain id="d_1" domain="example.com" status="verified">]
# Create a domain
domain = client.domains.create(domain: 'example.com')
# Get a domain by ID
domain = client.domains.get('d_1')
# Delete a domain
client.domains.delete('d_1')
# Trigger DNS verification
domain = client.domains.verify('d_1')
# Get required DNS records
records = client.domains.dns_records('d_1')
records.each { |r| puts "#{r.type} #{r.name} → #{r.value}" }# List aliases (paginated)
result = client.aliases.list(page: 1, limit: 10, search: 'hello')
result.data # => [CycleTask::Types::Alias, ...]
result.total # => 42
result.page # => 1
result.total_pages # => 5
result.next_page? # => true
# Iterate directly
result.each { |a| puts a.alias_address }
# Create an alias
a = client.aliases.create(
alias: 'hello',
domain_id: 'd_1',
destinations: ['me@gmail.com', 'backup@gmail.com'],
description: 'My main alias',
privacy_mode: false
)
# Update an alias
a = client.aliases.update('a_1', is_active: false, description: 'Disabled')
# Delete an alias
client.aliases.delete('a_1')
# Get forwarding logs for an alias
logs = client.aliases.logs('a_1')
logs.each { |l| puts "#{l.from} → #{l.status}" }| Parameter | Type | Description |
|---|---|---|
page |
Integer | Page number |
limit |
Integer | Items per page |
search |
String | Search by alias name |
domain |
String | Filter by domain ID |
status |
String | Filter: "active"/"inactive" |
sort |
String | Sort field |
# Overview statistics
overview = client.stats.overview
# => {"total_forwarded" => 1234, "total_blocked" => 56, ...}
# Chart data
chart = client.stats.chart(days: 30)
# Email logs (paginated)
logs = client.stats.logs(page: 1, limit: 20, status: 'delivered')
logs.each { |l| puts "#{l.subject} — #{l.status}" }These endpoints do not require authentication.
# Current system status
status = client.status.get
# => {"status" => "operational", "message" => "All systems normal"}
# Uptime metrics
uptime = client.status.uptime
# Recent incidents
incidents = client.status.incidentsAll API responses are wrapped in typed objects with method accessors:
domain = client.domains.get('d_1')
domain.id # => "d_1"
domain.domain # => "example.com"
domain.status # => "verified"
domain.is_verified # => true
domain.to_h # => {"id" => "d_1", "domain" => "example.com", ...}
domain["id"] # => "d_1" (bracket access for any field)Paginated responses implement Enumerable:
result = client.aliases.list
result.size # => 20
result.total # => 142
result.next_page? # => true
result.map(&:id) # => ["a_1", "a_2", ...]All errors inherit from CycleTask::Error:
begin
client.domains.create(domain: 'bad')
rescue CycleTask::AuthenticationError => e
# 401 — invalid or missing API key
puts e.message
rescue CycleTask::ValidationError => e
# 422 — validation failed
puts e.errors # => {"domain" => ["is invalid"]}
rescue CycleTask::NotFoundError => e
# 404 — resource not found
rescue CycleTask::RateLimitError => e
# 429 — rate limited (auto-retried by default)
rescue CycleTask::ServerError => e
# 5xx — server error (auto-retried by default)
rescue CycleTask::TimeoutError => e
# Request timed out
rescue CycleTask::ConnectionError => e
# Could not connect
rescue CycleTask::Error => e
# Catch-all for any other API error
puts e.status # HTTP status code
puts e.body # Parsed response body
endCycleTask::Error
├── CycleTask::AuthenticationError (401)
├── CycleTask::NotFoundError (404)
├── CycleTask::ValidationError (422)
├── CycleTask::RateLimitError (429)
├── CycleTask::ServerError (5xx)
├── CycleTask::TimeoutError
└── CycleTask::ConnectionError
Requests that receive 429 (rate limit) or 5xx (server error) responses are automatically retried with exponential backoff:
- Attempt 1: immediate
- Retry 1: 1 second delay
- Retry 2: 2 second delay
- Retry 3: 4 second delay
If the server provides a Retry-After header, that value is used instead. Configure with max_retries: 0 to disable.
- Ruby 3.0+
- No external dependencies (uses stdlib
net/httpandjson)
git clone https://github.com/cycletask/ruby-sdk.git
cd ruby-sdk
bundle install
bundle exec rspecMIT License. See LICENSE for details.