Ruby email client gem
The official Mailpipe Ruby gem provides an expressive, idiomatic interface for sending and receiving email. It integrates natively with Rails ActionMailer, Sidekiq for background delivery, and any Rack-based web framework.
Install via RubyGems or add to your Gemfile:
gem install mailpipe
gem "mailpipe", "~> 1.0"
Then run:
bundle install
Requires Ruby 3.1 or later. The gem uses net/http from the standard library with no additional runtime dependencies.
Configure the gem globally in an initializer (recommended for Rails):
require "mailpipe"
Mailpipe.configure do |config|
config.api_key = ENV.fetch("MAILPIPE_API_KEY")
config.timeout = 30 # seconds, default: 30
config.max_retries = 3 # retries on 5xx, default: 3
config.logger = Rails.logger # optional, for request logging
endOr instantiate the client directly for scoped configuration:
require "mailpipe" # Per-instance client client = Mailpipe::Client.new( api_key: ENV["MAILPIPE_API_KEY"], timeout: 10, ) # Or use the global singleton after calling Mailpipe.configure Mailpipe.client # => Mailpipe::Client instance
Send a transactional email using the global client or an instance:
require "mailpipe"
# Using the global client (after Mailpipe.configure)
message = Mailpipe.messages.send(
from: "noreply@yourdomain.com",
to: ["user@example.com"],
subject: "Welcome to our platform!",
html: "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
text: "Welcome! Thanks for signing up.",
)
puts "Sent: #{message.id}"
# => Sent: msg_01hx7k3p8n2qwertySend with CC, BCC, a named sender, and custom headers:
message = Mailpipe.messages.send(
from: { email: "invoices@acme.com", name: "Acme Billing" },
to: ["customer@example.com"],
cc: ["accountant@acme.com"],
bcc: ["archive@acme.com"],
reply_to: "billing@acme.com",
subject: "Your invoice #INV-2024-001",
html: invoice_html,
text: invoice_text,
headers: {
"X-Invoice-Id" => "INV-2024-001"
},
tags: ["invoice", "billing"],
)Retrieve mailboxes and enumerate messages with optional filters:
# List all mailboxes
response = Mailpipe.mailboxes.list
response.data.each do |mailbox|
puts "#{mailbox.email} — #{mailbox.unread_count} unread"
end
# List unread messages in a specific mailbox
messages = Mailpipe.messages.list(
mailbox_id: "mbx_01hx7k3abc",
unread: true,
limit: 25
)
puts "Found #{messages.data.size} of #{messages.meta.total} messages"
messages.data.each do |msg|
puts "[#{msg.received_at}] #{msg.subject} — from #{msg.from}"
end
# Paginate through all messages
page = Mailpipe.messages.list(limit: 50)
loop do
page.data.each { |msg| process(msg) }
break unless page.meta.has_more?
page = Mailpipe.messages.list(limit: 50, offset: page.meta.next_offset)
endConfigure Rails to use Mailpipe as your ActionMailer delivery method:
Rails.application.configure do
config.action_mailer.delivery_method = :mailpipe
config.action_mailer.mailpipe_settings = {
api_key: ENV.fetch("MAILPIPE_API_KEY"),
}
endYour existing mailers work unchanged:
class UserMailer < ApplicationMailer
default from: "noreply@yourdomain.com"
def welcome_email(user)
@user = user
@url = new_session_url
mail(
to: user.email,
subject: "Welcome to #{Rails.application.name}!"
)
end
def password_reset(user, token)
@user = user
@token = token
mail(
to: user.email,
subject: "Reset your password"
)
end
end
# Deliver in a controller or background job
UserMailer.welcome_email(user).deliver_later
UserMailer.password_reset(user, token).deliver_nowHandle incoming email events with a dedicated webhook controller:
# config/routes.rb post "/webhooks/mailpipe", to: "webhooks#mailpipe"
class WebhooksController < ApplicationController
# Skip CSRF protection for webhooks
skip_before_action :verify_authenticity_token, only: :mailpipe
def mailpipe
payload = request.body.read
signature = request.headers["X-Mailpipe-Signature"]
event = Mailpipe.webhooks.construct_event(
payload,
signature,
secret: ENV.fetch("MAILPIPE_WEBHOOK_SECRET")
)
case event.type
when "message.received"
IncomingEmailJob.perform_later(event.data.to_h)
when "message.bounced"
BounceHandlerJob.perform_later(event.data.recipient)
end
head :ok
rescue Mailpipe::WebhookSignatureError => e
Rails.logger.warn "Invalid webhook signature: #{e.message}"
head :bad_request
end
endAll exceptions inherit from Mailpipe::Error and can be rescued individually or together:
require "mailpipe"
begin
Mailpipe.messages.send(
from: "noreply@yourdomain.com",
to: ["user@example.com"],
subject: "Hello",
html: "<p>Hello</p>"
)
rescue Mailpipe::AuthenticationError
Rails.logger.error "Invalid Mailpipe API key"
rescue Mailpipe::RateLimitError => e
Rails.logger.warn "Rate limited — retry after #{e.retry_after}s"
# Re-enqueue Sidekiq job
raise Sidekiq::LimitFetch::StopRetry if e.retry_after > 60
rescue Mailpipe::ValidationError => e
Rails.logger.error "Validation failed: #{e.errors.inspect}"
rescue Mailpipe::NetworkError => e
Rails.logger.error "Network error: #{e.message}"
raise # re-raise for Sidekiq retry
rescue Mailpipe::Error => e
Rails.logger.error "Mailpipe error #{e.status}: #{e.message} (#{e.request_id})"
endMailpipe::Error ├── Mailpipe::APIError │ ├── Mailpipe::AuthenticationError # 401 │ ├── Mailpipe::PermissionError # 403 │ ├── Mailpipe::NotFoundError # 404 │ ├── Mailpipe::ValidationError # 422 │ ├── Mailpipe::RateLimitError # 429 │ └── Mailpipe::ServerError # 5xx ├── Mailpipe::NetworkError └── Mailpipe::WebhookSignatureError
Explore the full REST API documentation with all available endpoints.
View API Reference