Skip to main content
Requirements: Ruby 3.2.0 or higher

Installation

Add to your Gemfile:
gem "ark-email", "~> 0.5"
Then run:
bundle install
Or install directly:
gem install ark-email

Quick Start

require "ark_email"

# Initialize the client
client = ArkEmail::Client.new(api_key: ENV["ARK_API_KEY"])

# Send an email
email = client.emails.send_(
  from: "Security <[email protected]>",
  to: ["[email protected]"],
  subject: "Reset your password",
  html: "<h1>Password Reset</h1><p>Click the link below to reset your password.</p>",
  text: "Password Reset\n\nClick the link below to reset your password."
)

puts "Email ID: #{email.data.id}"
puts "Status: #{email.data.status}"
Ruby uses send_ (with underscore) because send is a reserved method in Ruby’s Object class.

Configuration

Client Options

require "ark_email"

client = ArkEmail::Client.new(
  api_key: "ark_...",              # Required (or set ARK_API_KEY env var)
  timeout: 60,                      # Request timeout in seconds (default: 60)
  max_retries: 2,                   # Number of retry attempts (default: 2)
  base_url: "https://api.arkhq.io/v1"  # API base URL (rarely needed)
)

Environment Variables

export ARK_API_KEY="ark_live_..."

API Reference

Emails

Send a single email.
email = client.emails.send_(
  from: "[email protected]",
  to: ["[email protected]"],
  subject: "Welcome!",
  html: "<h1>Hello</h1>",
  text: "Hello",  # Optional but recommended
  cc: ["[email protected]"],  # Optional
  bcc: ["[email protected]"],  # Optional
  reply_to: "[email protected]",  # Optional
  tags: ["welcome", "onboarding"],  # Optional
  metadata: { user_id: "123" },  # Optional
  track_opens: true,  # Optional
  track_clicks: true,  # Optional
  scheduled_at: "2024-01-20T09:00:00Z",  # Optional - ISO 8601
  attachments: [  # Optional
    {
      filename: "invoice.pdf",
      content: base64_encoded_content,
      content_type: "application/pdf"
    }
  ],
  headers: {  # Optional custom headers
    "X-Custom-Header" => "value"
  }
)
Returns: SendEmailResponse with data.id, data.status, etc.
Send multiple emails in a single request.
result = client.emails.send_batch(
  emails: [
    {
      from: "[email protected]",
      to: ["[email protected]"],
      subject: "Hello User 1",
      html: "<p>Hello!</p>"
    },
    {
      from: "[email protected]",
      to: ["[email protected]"],
      subject: "Hello User 2",
      html: "<p>Hello!</p>"
    }
  ]
)

result.data.each do |email|
  puts "#{email.id}: #{email.status}"
end
Send a raw MIME message.
raw_message = <<~MIME
  From: [email protected]
  To: [email protected]
  Subject: Raw email
  Content-Type: text/plain

  This is a raw MIME message.
MIME

email = client.emails.send_raw(
  from: "[email protected]",
  to: ["[email protected]"],
  raw_message: raw_message
)
List emails with filtering and pagination.
emails = client.emails.list(
  page: 1,
  per_page: 25,
  status: "sent",       # Optional filter
  tag: "welcome"        # Optional filter
)

emails.data.each do |email|
  puts "#{email.id}: #{email.subject} - #{email.status}"
end

# Pagination info
puts "Page #{emails.page} of #{emails.total_pages}"
Get a single email by ID.
email = client.emails.retrieve("msg_abc123xyz")

puts "Subject: #{email.data.subject}"
puts "Status: #{email.data.status}"
puts "Created: #{email.data.created_at}"
Get delivery attempts for an email.
deliveries = client.emails.get_deliveries("msg_abc123xyz")

deliveries.data.each do |delivery|
  puts "Attempt at #{delivery.timestamp}: #{delivery.status}"
  puts "  Error: #{delivery.error}" if delivery.error
end
Retry a failed email.
result = client.emails.retry("msg_abc123xyz")
puts "Retry scheduled: #{result.data.id}"

Domains

Register a new sending domain.
domain = client.domains.create(name: "mail.yourdomain.com")

puts "Domain ID: #{domain.data.id}"
puts "DNS Records to configure:"
domain.data.dns_records.each do |record|
  puts "  #{record.type} #{record.name} -> #{record.value}"
end
List all domains.
domains = client.domains.list

domains.data.each do |domain|
  status = domain.verified ? "Verified" : "Pending"
  puts "#{domain.name}: #{status}"
end
Get domain details.
domain = client.domains.retrieve("dom_abc123")
puts "Domain: #{domain.data.name}"
puts "Verified: #{domain.data.verified}"
Trigger DNS verification.
result = client.domains.verify("dom_abc123")

if result.data.verified
  puts "Domain verified successfully!"
else
  puts "DNS records not found. Please check your configuration."
end
Remove a domain.
client.domains.delete("dom_abc123")
puts "Domain deleted"

Suppressions

Add an email to the suppression list.
suppression = client.suppressions.create(
  email: "[email protected]",
  reason: "hard_bounce"
)
Add multiple emails to the suppression list.
result = client.suppressions.bulk_create(
  suppressions: [
    { email: "[email protected]", reason: "hard_bounce" },
    { email: "[email protected]", reason: "complaint" }
  ]
)
puts "Added #{result.data.created_count} suppressions"
List suppressed emails.
suppressions = client.suppressions.list(
  page: 1,
  per_page: 50,
  reason: "hard_bounce"  # Optional filter
)

suppressions.data.each do |s|
  puts "#{s.email}: #{s.reason} (added #{s.created_at})"
end
Check if an email is suppressed.
begin
  suppression = client.suppressions.retrieve("[email protected]")
  puts "Suppressed: #{suppression.data.reason}"
rescue ArkEmail::Errors::NotFoundError
  puts "Email is not suppressed"
end
Remove from suppression list.
client.suppressions.delete("[email protected]")
puts "Removed from suppression list"

Webhooks

Create a webhook endpoint.
webhook = client.webhooks.create(
  url: "https://yourapp.com/webhooks/ark",
  events: ["MessageSent", "MessageBounced", "MessageLoaded"]
)

puts "Webhook ID: #{webhook.data.id}"
puts "Signing Secret: #{webhook.data.signing_secret}"  # Store this!
List all webhooks.
webhooks = client.webhooks.list

webhooks.data.each do |wh|
  puts "#{wh.id}: #{wh.url}"
  puts "  Events: #{wh.events.join(', ')}"
end
Update webhook configuration.
webhook = client.webhooks.update(
  "wh_abc123",
  events: ["MessageSent", "MessageBounced"]  # Updated events
)
Send a test event to your webhook.
result = client.webhooks.test("wh_abc123", event: "MessageSent")
puts "Test result: #{result.data.status}"
Delete a webhook.
client.webhooks.delete("wh_abc123")

Tracking

Configure a custom tracking domain.
tracking = client.tracking.create(domain: "track.yourdomain.com")

puts "Add this CNAME record:"
puts "  #{tracking.data.cname_target}"
List tracking domains.
tracking_domains = client.tracking.list

tracking_domains.data.each do |t|
  status = t.verified ? "Verified" : "Pending"
  puts "#{t.domain}: #{status}"
end
Verify tracking domain DNS.
result = client.tracking.verify("trk_abc123")
puts "Verified: #{result.data.verified}"
Remove a tracking domain.
client.tracking.delete("trk_abc123")

Error Handling

The SDK throws typed exceptions for different error scenarios:
require "ark_email"

client = ArkEmail::Client.new

begin
  email = client.emails.send_(
    from: "[email protected]",
    to: ["invalid"],
    subject: "Test",
    html: "<p>Test</p>"
  )
rescue ArkEmail::Errors::BadRequestError => e
  puts "Invalid request: #{e.message}"
  puts "Error code: #{e.code}"
rescue ArkEmail::Errors::AuthenticationError
  puts "Invalid API key"
rescue ArkEmail::Errors::RateLimitError => e
  puts "Rate limited. Retry after: #{e.response.headers['Retry-After']}"
rescue ArkEmail::Errors::APIConnectionError
  puts "Network error - check your connection"
rescue ArkEmail::Errors::APIError => e
  puts "API error #{e.status}: #{e.message}"
end

Exception Hierarchy

ArkEmail::Errors::APIError
├── ArkEmail::Errors::APIConnectionError  # Network/connection issues
├── ArkEmail::Errors::APITimeoutError     # Request timeout
└── ArkEmail::Errors::APIStatusError      # HTTP error responses
    ├── ArkEmail::Errors::BadRequestError        # 400
    ├── ArkEmail::Errors::AuthenticationError    # 401
    ├── ArkEmail::Errors::PermissionDeniedError  # 403
    ├── ArkEmail::Errors::NotFoundError          # 404
    ├── ArkEmail::Errors::UnprocessableEntityError # 422
    ├── ArkEmail::Errors::RateLimitError         # 429
    └── ArkEmail::Errors::InternalServerError    # 5xx

Type Safety with Sorbet

The SDK includes comprehensive RBI type definitions for Sorbet:
# typed: strict
require "ark_email"

sig { params(to: String, subject: String, html: String).returns(ArkEmail::SendEmailResponse) }
def send_welcome_email(to, subject, html)
  client = ArkEmail::Client.new
  client.emails.send_(
    from: "[email protected]",
    to: [to],
    subject: subject,
    html: html
  )
end
The types work without requiring sorbet-runtime as a dependency.

Response Objects

Response models support multiple access patterns:
email = client.emails.retrieve("msg_abc123")

# Dot notation
puts email.data.subject

# Bracket notation
puts email[:data][:subject]

# Pattern matching (Ruby 3.0+)
case email
in { data: { status: "sent" } }
  puts "Email was sent!"
in { data: { status: "bounced" } }
  puts "Email bounced"
end

# Conversion methods
email.to_h    # Convert to Hash
email.to_json # Convert to JSON string
email.to_yaml # Convert to YAML string

Thread Safety

The client is thread-safe and maintains a connection pool of 99 connections. Use a single client instance across your application:
# config/initializers/ark.rb (Rails)
ARK_CLIENT = ArkEmail::Client.new(api_key: ENV["ARK_API_KEY"])

# app/services/email_service.rb
class EmailService
  def send_welcome(user)
    ARK_CLIENT.emails.send_(
      from: "[email protected]",
      to: [user.email],
      subject: "Welcome, #{user.name}!",
      html: render_template("welcome", user: user)
    )
  end
end

Concurrent Sending

require "ark_email"

client = ArkEmail::Client.new

users = User.where(welcome_sent: false).limit(100)

threads = users.map do |user|
  Thread.new do
    client.emails.send_(
      from: "[email protected]",
      to: [user.email],
      subject: "Welcome!",
      html: "<h1>Hello, #{user.name}!</h1>"
    )
  end
end

threads.each(&:join)

Framework Integration

Ruby on Rails

# config/initializers/ark.rb
Rails.application.config.ark_client = ArkEmail::Client.new(
  api_key: Rails.application.credentials.ark_api_key
)

# Helper method
module ArkHelper
  def ark_client
    Rails.application.config.ark_client
  end
end

# Include in ApplicationController
class ApplicationController < ActionController::Base
  include ArkHelper
end

# app/mailers/user_mailer.rb
class UserMailer
  include ArkHelper

  def welcome(user)
    ark_client.emails.send_(
      from: "[email protected]",
      to: [user.email],
      subject: "Welcome to #{Rails.application.config.app_name}!",
      html: ApplicationController.render(
        template: "user_mailer/welcome",
        locals: { user: user }
      )
    )
  end
end

Using with ActionMailer Pattern

Create a custom delivery method:
# lib/ark_delivery_method.rb
class ArkDeliveryMethod
  def initialize(settings)
    @client = ArkEmail::Client.new(api_key: settings[:api_key])
  end

  def deliver!(mail)
    @client.emails.send_(
      from: mail.from.first,
      to: mail.to,
      cc: mail.cc,
      bcc: mail.bcc,
      subject: mail.subject,
      html: mail.html_part&.body&.to_s,
      text: mail.text_part&.body&.to_s || mail.body.to_s
    )
  end
end

# config/initializers/ark.rb
ActionMailer::Base.add_delivery_method :ark, ArkDeliveryMethod,
  api_key: Rails.application.credentials.ark_api_key

# config/environments/production.rb
config.action_mailer.delivery_method = :ark

Sinatra

require "sinatra"
require "ark_email"

configure do
  set :ark, ArkEmail::Client.new
end

post "/send-email" do
  content_type :json

  email = settings.ark.emails.send_(
    from: "[email protected]",
    to: [params[:to]],
    subject: params[:subject],
    html: params[:html]
  )

  { email_id: email.data.id }.to_json
end

Hanami

# lib/yourapp/providers/ark.rb
Hanami.app.register_provider :ark do
  prepare do
    require "ark_email"
  end

  start do
    client = ArkEmail::Client.new(api_key: ENV["ARK_API_KEY"])
    register "ark.client", client
  end
end

# app/actions/emails/send.rb
module YourApp
  module Actions
    module Emails
      class Send < YourApp::Action
        include Deps["ark.client"]

        def handle(request, response)
          email = ark_client.emails.send_(
            from: "[email protected]",
            to: [request.params[:to]],
            subject: request.params[:subject],
            html: request.params[:html]
          )

          response.body = { email_id: email.data.id }.to_json
        end
      end
    end
  end
end

Advanced Usage

Custom HTTP Requests

For undocumented endpoints or custom requests:
response = client.request(
  method: :post,
  path: "/custom/endpoint",
  body: { key: "value" },
  query: { param: "value" },
  headers: { "X-Custom" => "header" }
)

Idempotency

Use idempotency keys to safely retry requests:
require "securerandom"

idempotency_key = SecureRandom.uuid

# This request can be safely retried with the same key
email = client.emails.send_(
  from: "[email protected]",
  to: ["[email protected]"],
  subject: "Order Confirmation",
  html: "<p>Your order is confirmed.</p>",
  idempotency_key: idempotency_key
)

Resources