A modern Ruby client library for the Laneful email API with support for sending emails, templates, tracking, and webhooks.
- Ruby 3.0 or higher
- Bundler (for dependency management)
Add the following to your Gemfile:
gem 'laneful-ruby'Then run:
bundle installgit clone https://github.com/lanefulhq/laneful-ruby.git
cd laneful-ruby
bundle install
bundle exec rake installrequire '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!"- 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
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)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)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)# 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)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)# 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)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)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 truePass 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 = Laneful::TrackingSettings.new(
opens: true,
clicks: true,
unsubscribes: true,
unsubscribe_group_id: 123,
# ignored if unsubscribe_group_id is set
unsubscribe_group_name: 'Newsletters'
)client = Laneful::Client.new(
'https://your-endpoint.send.laneful.net',
'your-auth-token',
timeout: 60 # 60 second timeout
)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'
)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')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')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'))# 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.
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}"
endLaneful::Client.new(base_url, auth_token, timeout: 30)base_url- The base URL of the Laneful APIauth_token- The authentication tokentimeout- Request timeout in seconds (optional, default: 30)
send_email(email, mail_settings = nil)- Sends a single emailsend_emails(emails, mail_settings = nil)- Sends multiple emailslist_unsubscribe_groups/create_unsubscribe_group/update_unsubscribe_grouplist_domains/get_domain/create_domain/update_domain/verify_domain/delete_domainlist_domain_spam_ratio_radar/list_google_postmaster_spam_reports/list_snds_reports
from(address)- Sender addresssubject(subject)- Email subject (also required for template emails)
from_header(address)- Visible From headerto(address)- Recipient addressescc(address)- CC addressesbcc(address)- BCC addressestext_content(content)- Plain text contenthtml_content(content)- HTML contenttemplate_id(id)- Template IDtemplate_data(data)- Template dataattachment(attachment)- File attachmentsheaders(headers)- Custom headersreply_to(address)- Reply-to addresssend_time(time)- Scheduled send time (Unix timestamp)webhook_data(data)- Webhook datatag(tag)- Email tagtracking(tracking)- Tracking settings
Laneful::Address.new(email, name = nil)email- The email address (required)name- The display name (optional)
# From file
Laneful::Attachment.from_file(file_path)
# From raw data
Laneful::Attachment.new(filename, content_type, content)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.verify_signature(secret, payload, signature)
Laneful::WebhookVerifier.parse_webhook_payload(payload) # => { is_batch:, events: [event hashes with every field] }Laneful::ValidationException- Thrown when input validation failsLaneful::ApiException- Thrown when the API returns an error responseLaneful::HttpException- Thrown when HTTP communication failsLaneful::LanefulException- Base exception class for all SDK exceptions
bundle exec rspecbundle exec rubocopbundle exec yard docThis project is licensed under the MIT License - see the LICENSE file for details.