Unofficial Ruby SDK for the Lettermint transactional email API. Based on the official Python SDK.
Add to your Gemfile:
gem 'lettermint'Or install directly:
gem install lettermintrequire 'lettermint'
client = Lettermint::Client.new(api_token: 'your-api-token')
response = client.email
.from('sender@example.com')
.to('recipient@example.com')
.subject('Hello from Ruby')
.html('<h1>Welcome!</h1>')
.text('Welcome!')
.deliver
puts response.message_idclient.email
.from('sender@example.com')
.to('recipient1@example.com', 'recipient2@example.com')
.subject('Hello')
.deliverclient.email
.from('sender@example.com')
.to('recipient@example.com')
.cc('cc1@example.com', 'cc2@example.com')
.bcc('bcc@example.com')
.subject('Hello')
.deliverclient.email
.from('sender@example.com')
.to('recipient@example.com')
.reply_to('reply@example.com')
.subject('Hello')
.deliverclient.email
.from('John Doe <john@example.com>')
.to('Jane Doe <jane@example.com>')
.subject('Hello')
.deliverrequire 'base64'
content = Base64.strict_encode64(File.binread('document.pdf'))
# Regular attachment
client.email
.from('sender@example.com')
.to('recipient@example.com')
.subject('Your Document')
.attach('document.pdf', content)
.deliver
# Inline attachment (for embedding in HTML)
client.email
.from('sender@example.com')
.to('recipient@example.com')
.subject('Welcome')
.html('<img src="cid:logo@example.com">')
.attach('logo.png', logo_content, content_id: 'logo@example.com')
.deliverYou can also use the EmailAttachment value object:
attachment = Lettermint::EmailAttachment.new(
filename: 'document.pdf',
content: content,
content_id: nil
)
client.email
.from('sender@example.com')
.to('recipient@example.com')
.attach(attachment)
.deliverclient.email
.from('sender@example.com')
.to('recipient@example.com')
.subject('Hello')
.headers({ 'X-Custom-Header' => 'value' })
.deliverclient.email
.from('sender@example.com')
.to('recipient@example.com')
.subject('Hello')
.metadata({ campaign_id: '123', user_id: '456' })
.tag('welcome-campaign')
.deliverclient.email
.from('sender@example.com')
.to('recipient@example.com')
.subject('Hello')
.route('my-route')
.deliverPrevent duplicate sends when retrying failed requests:
client.email
.from('sender@example.com')
.to('recipient@example.com')
.subject('Hello')
.idempotency_key('unique-request-id')
.deliverVerify webhook signatures to ensure authenticity:
require 'lettermint'
webhook = Lettermint::Webhook.new(secret: 'your-webhook-secret')
# Verify using headers (recommended)
payload = webhook.verify_headers(request.headers, request.body)
# Or verify using the signature directly
payload = webhook.verify(
request.body,
request.headers['X-Lettermint-Signature']
)
puts payload['event']For one-off verification:
payload = Lettermint::Webhook.verify_signature(
request.body,
request.headers['X-Lettermint-Signature'],
secret: 'your-webhook-secret'
)Adjust the timestamp tolerance (default: 300 seconds):
webhook = Lettermint::Webhook.new(secret: 'your-webhook-secret', tolerance: 600)The webhook verifier validates timestamp freshness and HMAC integrity but does not
track previously seen deliveries. Within the tolerance window (default 300 seconds),
a captured request could be replayed. Your application should deduplicate incoming
webhooks using the x-lettermint-delivery header value as an idempotency key -- for
example, by recording processed delivery IDs in a database or cache and rejecting
duplicates.
require 'lettermint'
client = Lettermint::Client.new(api_token: 'your-api-token')
begin
response = client.email
.from('sender@example.com')
.to('recipient@example.com')
.subject('Hello')
.deliver
rescue Lettermint::AuthenticationError => e
# 401/403 errors (invalid or revoked token)
puts "Auth error #{e.status_code}: #{e.message}"
rescue Lettermint::RateLimitError => e
# 429 errors
puts "Rate limited, retry after: #{e.retry_after}s"
rescue Lettermint::ValidationError => e
# 422 errors (e.g., daily limit exceeded)
puts "Validation error: #{e.error_type}"
puts "Response: #{e.response_body}"
rescue Lettermint::ClientError => e
# 400 errors
puts "Client error: #{e.message}"
rescue Lettermint::TimeoutError => e
# Request timeout
puts "Timeout: #{e.message}"
rescue Lettermint::HttpRequestError => e
# Other HTTP errors
puts "HTTP error #{e.status_code}: #{e.message}"
endbegin
payload = webhook.verify_headers(headers, body)
rescue Lettermint::InvalidSignatureError
puts 'Invalid signature - request may be forged'
rescue Lettermint::TimestampToleranceError
puts 'Timestamp too old - possible replay attack'
rescue Lettermint::WebhookJsonDecodeError => e
puts "Invalid JSON in payload: #{e.original_exception}"
rescue Lettermint::WebhookVerificationError => e
puts "Verification failed: #{e.message}"
endSet defaults once at application boot (e.g., in a Rails initializer):
Lettermint.configure do |config|
config.base_url = 'https://custom.api.com/v1'
config.timeout = 60
endAll clients created afterward inherit these defaults:
client = Lettermint::Client.new(api_token: 'your-api-token')
# Uses the global base_url and timeoutExplicit keyword arguments take precedence over global configuration:
client = Lettermint::Client.new(
api_token: 'your-api-token',
base_url: 'https://other.api.com/v1',
timeout: 10
)client = Lettermint::Client.new(api_token: 'your-api-token') do |config|
config.base_url = 'https://custom.api.com/v1'
config.timeout = 60
end- Ruby >= 3.2
- Faraday ~> 2.0
MIT