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

📚 Mailto Guides

💻 Other Language Guides

📖 Deep Dives


Test your Ruby implementation with our free mailto link generator.

← Back to Resources | Try Generator →

Have feedback? We'd love to hear it!