Laneful Ruby SDK
A modern Ruby client library for the Laneful email API with support for sending emails, templates, tracking, and webhooks.
Requirements
- Ruby 3.0 or higher
- Bundler (for dependency management)
Installation
Using Bundler
Add the following to your Gemfile:
gem 'laneful-ruby'Then run:
bundle installManual Installation
git clone https://github.com/lanefulhq/laneful-ruby.git
cd laneful-ruby
bundle install
bundle exec rake installQuick Start
require 'laneful'
# Create client
client = Laneful::Client.new(
'https://your-endpoint.send.laneful.net',
'your-auth-token'
)
# Create email
email = Laneful::Email::Builder.new
.from(Laneful::Address.new('sender@example.com', 'Your Name'))
.to(Laneful::Address.new('recipient@example.com', 'Recipient Name'))
.subject('Hello from Laneful Ruby SDK!')
.text_content('This is a simple test email.')
.build
# Send email
response = client.send_email(email)
puts "Email sent successfully!"Features
- Send single or multiple emails
- Plain text and HTML content
- Email templates with dynamic data
- File attachments
- Email tracking (opens, clicks, unsubscribes)
- Custom headers and reply-to addresses
- Visible
from_headerand request-levelmail_settings - Scheduled sending
- Webhook signature verification
- Domain management (list, create, verify, update email track, delete)
- Unsubscribe groups
- Deliverability analytics (spam-ratio radar, Google Postmaster, Microsoft SNDS)
- Comprehensive error handling
- Modern Ruby 3.0+ features
Examples
Simple Text Email
email = Laneful::Email::Builder.new
.from(Laneful::Address.new('sender@example.com'))
.to(Laneful::Address.new('user@example.com'))
.subject('Simple Email')
.text_content('This is a simple text email.')
.build
response = client.send_email(email)HTML Email with Tracking
tracking = Laneful::TrackingSettings.new(opens: true, clicks: true, unsubscribes: false)
email = Laneful::Email::Builder.new
.from(Laneful::Address.new('sender@example.com'))
.to(Laneful::Address.new('user@example.com'))
.subject('HTML Email with Tracking')
.html_content('<h1>Welcome!</h1><p>This is an <strong>HTML email</strong> with tracking enabled.</p>')
.text_content('Welcome! This is an HTML email with tracking enabled.')
.tracking(tracking)
.build
response = client.send_email(email)Template Email
email = Laneful::Email::Builder.new
.from(Laneful::Address.new('sender@example.com'))
.to(Laneful::Address.new('user@example.com'))
.subject('Welcome!') # Required, also for template emails
.template_id('welcome-template')
.template_data({
'name' => 'John Doe',
'company' => 'Acme Corp'
})
.build
response = client.send_email(email)Email with Attachments
# Create attachment from file
attachment = Laneful::Attachment.from_file('/path/to/document.pdf')
email = Laneful::Email::Builder.new
.from(Laneful::Address.new('sender@example.com'))
.to(Laneful::Address.new('user@example.com'))
.subject('Document Attached')
.text_content('Please find the document attached.')
.attachment(attachment)
.build
response = client.send_email(email)Multiple Recipients
email = Laneful::Email::Builder.new
.from(Laneful::Address.new('sender@example.com'))
.to(Laneful::Address.new('user1@example.com'))
.to(Laneful::Address.new('user2@example.com', 'User Two'))
.cc(Laneful::Address.new('cc@example.com'))
.bcc(Laneful::Address.new('bcc@example.com'))
.subject('Multiple Recipients')
.text_content('This email has multiple recipients.')
.build
response = client.send_email(email)Scheduled Email
# Schedule for 24 hours from now
send_time = Time.now.to_i + (24 * 60 * 60)
email = Laneful::Email::Builder.new
.from(Laneful::Address.new('sender@example.com'))
.to(Laneful::Address.new('user@example.com'))
.subject('Scheduled Email')
.text_content('This email was scheduled.')
.send_time(send_time)
.build
response = client.send_email(email)Multiple Emails
emails = [
Laneful::Email::Builder.new
.from(Laneful::Address.new('sender@example.com'))
.to(Laneful::Address.new('user1@example.com'))
.subject('Email 1')
.text_content('First email content.')
.build,
Laneful::Email::Builder.new
.from(Laneful::Address.new('sender@example.com'))
.to(Laneful::Address.new('user2@example.com'))
.subject('Email 2')
.text_content('Second email content.')
.build
]
response = client.send_emails(emails)Mail settings (sandbox and message IDs)
response = client.send_email(
email,
Laneful::MailSettings.new(sandbox_mode: true, return_message_ids: true)
)
# response['message_ids'] is present when return_message_ids is trueDuplicate send protection
Pass an idempotency_key (1 to 36 bytes) and reuse it when you retry a send, for example after a timeout. The client does not retry on its own.
response = client.send_email(
email,
Laneful::MailSettings.new(idempotency_key: "order-#{order_id}")
)Laneful remembers a key per organization for one hour. A repeated request with the same key gets status accepted with no message IDs and sends nothing, even if the emails differ, so use a new key for every distinct send. Detection is best effort. A request that fails with HTTP 500 doesn't keep its key, so a retry with it is processed as a new request: before resending after a 500, check your webhook events (a request event means Laneful accepted the email). Sandbox mode requests are not deduplicated. A key outside 1 to 36 bytes raises Laneful::ValidationException.
Tracking with an unsubscribe group
tracking = Laneful::TrackingSettings.new(
opens: true,
clicks: true,
unsubscribes: true,
unsubscribe_group_id: 123,
# ignored if unsubscribe_group_id is set
unsubscribe_group_name: 'Newsletters'
)Custom Timeout
client = Laneful::Client.new(
'https://your-endpoint.send.laneful.net',
'your-auth-token',
timeout: 60 # 60 second timeout
)Domain, unsubscribe groups, and analytics
These endpoints live on the organization API host. Point the client at it
(https://api.laneful.net):
client = Laneful::Client.new(
'https://api.laneful.net',
'your-auth-token'
)Unsubscribe groups
groups = client.list_unsubscribe_groups(42, Laneful::ListUnsubscribeGroupsParams.new(limit: 50))
created = client.create_unsubscribe_group(42, 'Newsletters')
updated = client.update_unsubscribe_group(42, created.unsubscribe_group_id, 'Weekly Newsletters')Domains
list = client.list_domains(42, Laneful::ListDomainsParams.new(limit: 50))
domain = client.create_domain(42, Laneful::CreateDomainRequest.new(
domain: 'mydomain.com',
tracking: 'tracking',
return_path: 'return-path'
))
domain = client.get_domain(42, 'mydomain.com')
domain = client.verify_domain(42, 'mydomain.com')
# Set the email track; pass '' to clear it, or omit email_track_id to leave it unchanged
domain = client.update_domain(42, 'mydomain.com', Laneful::UpdateDomainRequest.new(
'e59f0a35-05bc-4516-b585-c06f69c3e67e'
))
client.delete_domain(42, 'mydomain.com')Deliverability analytics
radar = client.list_domain_spam_ratio_radar(
Laneful::ListDomainSpamRatioRadarParams.new(
workspace_ids: [1, 2],
domain: 'example.com',
start_date: '2026-09-01',
end_date: '2026-09-08'
)
)
postmaster = client.list_google_postmaster_spam_reports(
Laneful::ListGooglePostmasterSpamReportsParams.new(domain: 'example.com')
)
snds = client.list_snds_reports(Laneful::ListSndsReportsParams.new(ip: '203.0.113.5'))Webhook Verification
# In your webhook handler
payload = request.body.read
signature = request.headers['x-webhook-signature']
secret = 'your-webhook-secret'
if Laneful::WebhookVerifier.verify_signature(secret, payload, signature)
# A single event or a batch (JSON array)
webhook_data = Laneful::WebhookVerifier.parse_webhook_payload(payload)
webhook_data[:events].each do |event|
# Handle each event, e.g. by event['event']
end
else
# Invalid signature
head :unauthorized
endEach parsed event contains every field Laneful sends. Every event has id, event, email, message_id, lane_id, tag, timestamp and metadata (the webhook_data sent with the email); lane_id is empty on request events. Event-specific fields such as url, client_ip, is_machine, code, is_hard, classification, reason and mx_host are included too. id is unique per event and stays the same when Laneful delivers the event again on retry or in a batch, so use it to skip events you have already processed. Events of types the SDK doesn't list are kept rather than rejecting the payload, so skip the ones you don't handle.
See examples/src/webhook_receiver_example.rb for a runnable HTTP receiver.
Error Handling
begin
response = client.send_email(email)
puts "Email sent successfully"
rescue Laneful::ValidationException => e
puts "Validation error: #{e.message}"
rescue Laneful::ApiException => e
puts "API error: #{e.message}"
puts "Status code: #{e.status_code}"
puts "Error message: #{e.error_message}"
rescue Laneful::HttpException => e
puts "HTTP error: #{e.message}"
puts "Status code: #{e.status_code}"
rescue StandardError => e
puts "Unexpected error: #{e.message}"
endAPI Reference
Laneful::Client
Constructor
Laneful::Client.new(base_url, auth_token, timeout: 30)-
base_url- The base URL of the Laneful API -
auth_token- The authentication token -
timeout- Request timeout in seconds (optional, default: 30)
Methods
-
send_email(email, mail_settings = nil)- Sends a single email -
send_emails(emails, mail_settings = nil)- Sends multiple emails -
list_unsubscribe_groups/create_unsubscribe_group/update_unsubscribe_group -
list_domains/get_domain/create_domain/update_domain/verify_domain/delete_domain -
list_domain_spam_ratio_radar/list_google_postmaster_spam_reports/list_snds_reports
Laneful::Email::Builder
Required Fields
-
from(address)- Sender address -
subject(subject)- Email subject (also required for template emails)
Optional Fields
-
from_header(address)- Visible From header -
to(address)- Recipient addresses -
cc(address)- CC addresses -
bcc(address)- BCC addresses -
text_content(content)- Plain text content -
html_content(content)- HTML content -
template_id(id)- Template ID -
template_data(data)- Template data -
attachment(attachment)- File attachments -
headers(headers)- Custom headers -
reply_to(address)- Reply-to address -
send_time(time)- Scheduled send time (Unix timestamp) -
webhook_data(data)- Webhook data -
tag(tag)- Email tag -
tracking(tracking)- Tracking settings
Laneful::Address
Laneful::Address.new(email, name = nil)-
email- The email address (required) -
name- The display name (optional)
Laneful::Attachment
# From file
Laneful::Attachment.from_file(file_path)
# From raw data
Laneful::Attachment.new(filename, content_type, content)Laneful::TrackingSettings
Laneful::TrackingSettings.new(
opens: false,
clicks: false,
unsubscribes: false,
unsubscribe_group_id: nil,
unsubscribe_group_name: nil
)
Laneful::MailSettings.new(sandbox_mode: true, return_message_ids: true)
Laneful::MailSettings.new(idempotency_key: 'order-1234') # 1 to 36 bytesLaneful::WebhookVerifier
Laneful::WebhookVerifier.verify_signature(secret, payload, signature)
Laneful::WebhookVerifier.parse_webhook_payload(payload) # => { is_batch:, events: [event hashes with every field] }Exception Types
-
Laneful::ValidationException- Thrown when input validation fails -
Laneful::ApiException- Thrown when the API returns an error response -
Laneful::HttpException- Thrown when HTTP communication fails -
Laneful::LanefulException- Base exception class for all SDK exceptions
Development
Running Tests
bundle exec rspecCode Quality
bundle exec rubocopDocumentation
bundle exec yard docLicense
This project is licensed under the MIT License - see the LICENSE file for details.