Ruby Mailto Link Generator: Complete Guide
Learn how to generate perfect mailto links in Ruby with Rails integration and proper encoding.
Quick Start (Pure Ruby)
require 'cgi'
def create_mailto(to, subject: '', body: '', cc: '', bcc: '')
params = []
params << "subject=#{CGI.escape(subject)}" unless subject.empty?
params << "body=#{CGI.escape(body)}" unless body.empty?
params << "cc=#{CGI.escape(cc)}" unless cc.empty?
params << "bcc=#{CGI.escape(bcc)}" unless bcc.empty?
query = params.empty? ? '' : "?#{params.join('&')}"
"mailto:#{to}#{query}"
end
# Usage
mailto = create_mailto(
'[email protected]',
subject: 'Bug Report',
body: "Description:\n- Issue: Login failed\n- Browser: Chrome"
)
puts mailto
# mailto:[email protected]?subject=Bug+Report&body=Description%3A%0A-+Issue%3A+Login+failed%0A-+Browser%3A+Chrome
Ruby on Rails Integration
Helper Method
# app/helpers/mailto_helper.rb
module MailtoHelper
def mailto_link(to, subject: '', body: '', cc: '', bcc: '', text: nil)
params = {}
params[:subject] = subject unless subject.empty?
params[:body] = body unless body.empty?
params[:cc] = cc unless cc.empty?
params[:bcc] = bcc unless bcc.empty?
query = params.map { |k, v| "#{k}=#{ERB::Util.url_encode(v)}" }.join('&')
href = query.empty? ? "mailto:#{to}" : "mailto:#{to}?#{query}"
link_text = text || to
link_to link_text, href, class: 'mailto-link'
end
end
# Usage in view
<%= mailto_link('[email protected]',
subject: 'Help Request',
body: "Hi,\n\nI need help with...",
text: '📧 Contact Support') %>
Controller Integration
# app/controllers/contact_controller.rb
class ContactController < ApplicationController
def show
@support_mailto = MailtoBuilder.new('[email protected]')
.subject('Support Request')
.body("Please describe your issue:\n\n")
.build
end
def generate
builder = MailtoBuilder.new(params[:to])
builder.subject(params[:subject]) if params[:subject].present?
builder.body(params[:body]) if params[:body].present?
render json: {
success: true,
mailto: builder.build,
stats: builder.stats
}
rescue => e
render json: { success: false, error: e.message }, status: :bad_request
end
end
Object-Oriented Approach
class MailtoBuilder
attr_reader :to, :subject, :body, :cc, :bcc
MAX_LENGTH = 1800
def initialize(to)
raise ArgumentError, 'Invalid email' unless valid_email?(to)
@to = to
@subject = ''
@body = ''
@cc = ''
@bcc = ''
end
def subject(text)
@subject = text
self
end
def body(text)
@body = text
self
end
def cc(email)
@cc = email
self
end
def bcc(email)
@bcc = email
self
end
def build
parts = []
parts << "subject=#{encode(@subject)}" unless @subject.empty?
parts << "body=#{encode(@body)}" unless @body.empty?
parts << "cc=#{encode(@cc)}" unless @cc.empty?
parts << "bcc=#{encode(@bcc)}" unless @bcc.empty?
query = parts.empty? ? '' : "?#{parts.join('&')}"
mailto = "mailto:#{@to}#{query}"
raise "URL too long: #{mailto.length} chars" if mailto.length > MAX_LENGTH
mailto
end
def stats
mailto = build
{
total_length: mailto.length,
max_length: MAX_LENGTH,
remaining: MAX_LENGTH - mailto.length,
percentage: (mailto.length.to_f / MAX_LENGTH * 100).round(2)
}
end
private
def encode(text)
ERB::Util.url_encode(text)
end
def valid_email?(email)
email =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
end
end
# Usage
builder = MailtoBuilder.new('[email protected]')
mailto = builder.subject('Bug Report')
.body("Issue description\nWith line breaks")
.build
puts mailto
puts builder.stats
Rails View Templates
ERB Example
<!-- app/views/contact/show.html.erb -->
<div class="contact-section">
<h1>Contact Us</h1>
<!-- Simple mailto -->
<%= link_to '📧 Email Support',
"mailto:[email protected]?subject=#{ERB::Util.url_encode('Help Request')}",
class: 'btn btn-primary' %>
<!-- Using helper -->
<%= mailto_link('[email protected]',
subject: 'Sales Inquiry',
body: "Company:\nInterest:",
text: '💼 Contact Sales') %>
<!-- Dynamic form -->
<%= form_with url: generate_mailto_path, method: :post, local: false do |f| %>
<%= f.email_field :to, placeholder: 'To:', required: true %>
<%= f.text_field :subject, placeholder: 'Subject:' %>
<%= f.text_area :body, rows: 5, placeholder: 'Message:' %>
<%= f.submit 'Generate Mailto Link' %>
<% end %>
<div id="mailto-result"></div>
</div>
<script>
document.querySelector('form').addEventListener('ajax:success', (event) => {
const { mailto } = event.detail[0];
document.getElementById('mailto-result').innerHTML =
`<a href="${mailto}">${mailto}</a>`;
});
</script>
HAML Example
-# app/views/contact/show.html.haml
.contact-section
%h1 Contact Us
= link_to '📧 Email Us',
mailto_link_helper('[email protected]', 'Contact Request'),
class: 'btn btn-primary'
%form#mailto-builder
= text_field_tag :to, '', placeholder: 'To:', required: true
= text_field_tag :subject, '', placeholder: 'Subject:'
= text_area_tag :body, '', rows: 5, placeholder: 'Message:'
= submit_tag 'Generate'
#result
Template System
class MailtoTemplate
TEMPLATES = {
support: {
to: '[email protected]',
subject: 'Support Request: %{issue}',
body: "Hi Support Team,\n\nIssue: %{issue}\nUser: %{user}\n\nDetails:\n%{details}"
},
feedback: {
to: '[email protected]',
subject: 'Product Feedback',
body: "Rating: %{rating}/5\n\nFeedback:\n%{comment}"
}
}.freeze
def self.render(template_name, vars = {})
template = TEMPLATES[template_name]
raise "Template not found: #{template_name}" unless template
subject = template[:subject] % vars
body = template[:body] % vars
MailtoBuilder.new(template[:to])
.subject(subject)
.body(body)
.build
end
end
# Usage
mailto = MailtoTemplate.render(:support,
issue: 'Login Error',
user: '[email protected]',
details: 'Cannot log in after password reset'
)
API Endpoint (Rails)
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
post 'mailto', to: 'mailto#create'
end
end
end
# app/controllers/api/v1/mailto_controller.rb
module Api
module V1
class MailtoController < ApplicationController
skip_before_action :verify_authenticity_token
def create
builder = MailtoBuilder.new(mailto_params[:to])
builder.subject(mailto_params[:subject]) if mailto_params[:subject].present?
builder.body(mailto_params[:body]) if mailto_params[:body].present?
builder.cc(mailto_params[:cc]) if mailto_params[:cc].present?
render json: {
success: true,
mailto: builder.build,
stats: builder.stats
}
rescue => e
render json: {
success: false,
error: e.message
}, status: :bad_request
end
private
def mailto_params
params.permit(:to, :subject, :body, :cc, :bcc)
end
end
end
end
Testing (RSpec)
# spec/helpers/mailto_helper_spec.rb
require 'rails_helper'
RSpec.describe MailtoHelper, type: :helper do
describe '#mailto_link' do
it 'creates basic mailto' do
result = helper.mailto_link('[email protected]')
expect(result).to include('mailto:[email protected]')
end
it 'encodes subject' do
result = helper.mailto_link('[email protected]', subject: 'Hello World')
expect(result).to include('subject=Hello+World')
end
it 'encodes line breaks' do
result = helper.mailto_link('[email protected]', body: "Line 1\nLine 2")
expect(result).to include('%0A')
end
end
end
# spec/lib/mailto_builder_spec.rb
require 'rails_helper'
RSpec.describe MailtoBuilder do
describe '#build' do
it 'creates basic mailto' do
builder = MailtoBuilder.new('[email protected]')
expect(builder.build).to eq('mailto:[email protected]')
end
it 'adds subject' do
builder = MailtoBuilder.new('[email protected]')
result = builder.subject('Hello').build
expect(result).to include('subject=')
end
it 'rejects invalid email' do
expect {
MailtoBuilder.new('invalid-email')
}.to raise_error(ArgumentError)
end
it 'validates length' do
builder = MailtoBuilder.new('[email protected]')
expect {
builder.body('x' * 2000).build
}.to raise_error(/too long/)
end
end
end
Sinatra Integration
require 'sinatra'
require 'cgi'
helpers do
def create_mailto(to, options = {})
params = []
params << "subject=#{CGI.escape(options[:subject])}" if options[:subject]
params << "body=#{CGI.escape(options[:body])}" if options[:body]
query = params.empty? ? '' : "?#{params.join('&')}"
"mailto:#{to}#{query}"
end
end
get '/contact' do
@mailto = create_mailto('[email protected]',
subject: 'Contact from Website',
body: "Hi,\n\nI found your site..."
)
erb :contact
end
post '/api/mailto' do
content_type :json
mailto = create_mailto(params[:to],
subject: params[:subject],
body: params[:body]
)
{ success: true, mailto: mailto }.to_json
end
Common Pitfalls
# ❌ WRONG: Using URI.encode (deprecated)
mailto = "mailto:#{to}?subject=#{URI.encode(subject)}"
# ✅ CORRECT: Use ERB::Util.url_encode or CGI.escape
mailto = "mailto:#{to}?subject=#{ERB::Util.url_encode(subject)}"
# ❌ WRONG: Not handling special characters
mailto = "mailto:#{to}?subject=#{subject}"
# ✅ CORRECT: Always encode
mailto = "mailto:#{to}?subject=#{CGI.escape(subject)}"
# ❌ WRONG: Forgetting to validate email
mailto = create_mailto(params[:email])
# ✅ CORRECT: Validate first
if params[:email] =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
mailto = create_mailto(params[:email])
end
Related Resources
📚 Mailto Guides
💻 Other Language Guides
- Python Guide - Flask, Django, FastAPI integration
- JavaScript Guide - React, Vue, Node.js, TypeScript
- PHP Guide - Laravel, WordPress, pure PHP
📖 Deep Dives
Test your Ruby implementation with our free mailto link generator.