How to Build a Ruby on Rails Contact Form With Action Mailer
A well‑designed contact form is essential for any Ruby on Rails portfolio or production app. In this guide you will build a contact form that saves submissions, shows clear success and error alerts, emails you when someone gets in touch, and sends the user a confirmation email so they know their message was received.
This tutorial uses Action Mailer in Rails 7+, Simple Form for cleaner form markup, and an SMTP provider (such as Proton, Gmail, or another service) for sending email.
1. Create the Contact model
Start by generating a simple Contact model to store submissions and handle basic validation.
rails generate model Contact first_name:string last_name:string email:string message:text
rails db:migrate
Open app/models/contact.rb and add presence and email format validations.
class Contact < ApplicationRecord
validates :first_name, :last_name, :email, :message, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
end
The presence validations prevent empty submissions, and the URI::MailTo::EMAIL_REGEXP constant provides a built‑in, reasonable email pattern without you having to craft a custom regular expression.
2. Set up routes and controller
Next, wire up a minimal ContactsController and routes so your form can POST to a dedicated endpoint.
Generate the controller:
rails generate controller Contacts
In config/routes.rb add:
Rails.application.routes.draw do
resources :contacts, only: [:create]
root 'pages#home'
end
This keeps a single create action for the contact form while the form itself will live on the home page (PagesController#home), which is set as the root path.
3. Build the contact form on the home page
Expose a fresh Contact object from the home action so the form builder knows what model it is working with.
In app/controllers/pages_controller.rb:
class PagesController < ApplicationController
def home
@contact = Contact.new
end
end
Now add the contact form to app/views/pages/home.html.erb. This example assumes you have Simple Form installed.
<section id='contact' class='py-5'>
<div class='row justify-content-center'>
<div class='col-lg-8'>
<h2 class='mb-4'>Contact me</h2>
<%= simple_form_for @contact, url: contacts_path, local: true do |f| %>
<div class='row'>
<div class='col-md-6'>
<%= f.input :first_name, label: 'First name' %>
</div>
<div class='col-md-6'>
<%= f.input :last_name, label: 'Last name' %>
</div>
</div>
<%= f.input :email, label: 'Your email' %>
<%= f.input :message, as: :text, rows: 6, label: 'Message' %>
<div class='mt-3'>
<%= f.button :submit, 'Send message', class: 'btn btn-primary' %>
</div>
<% end %>
</div>
</div>
</section>
The simple_form_for @contact, url: contacts_path call generates a POST form that submits to /contacts, which maps to ContactsController#create. The rows: 6 option simply controls how tall the textarea appears.
4. Generate the Action Mailer
Action Mailer ships with Rails and is designed specifically for building and sending emails. Generate a mailer with two actions: one for notifying you, and one for sending a confirmation email to the user.
rails generate mailer ContactMailer received_email confirmation_email
In app/mailers/application_mailer.rb, set a default sender address for your app:
class ApplicationMailer < ActionMailer::Base
default from: 'hello@yourdomain.com'
layout 'mailer'
end
Now define how each contact email is composed in app/mailers/contact_mailer.rb:
class ContactMailer < ApplicationMailer
def received_email
@contact = params[:contact]
mail(
to: 'hello@yourdomain.com',
subject: "New contact: #{@contact.first_name} #{@contact.last_name}",
reply_to: @contact.email
)
end
def confirmation_email
@contact = params[:contact]
mail(
to: @contact.email,
subject: 'Thanks for getting in touch'
)
end
end
The .with(contact: @contact) call you will use later in the controller passes the contact record into the mailer. Inside the mailer, you access it via params[:contact] and assign it to @contact for use in the views. Setting reply_to: @contact.email means hitting reply in your inbox goes straight back to the person who submitted the form.
5. Create email templates
Action Mailer uses views just like controllers do. The generator already created folders and starter files, which you can now customise.
Admin notification: app/views/contact_mailer/received_email.html.erb
<h1>New contact form submission</h1>
<p><strong>Name:</strong>
<%= @contact.first_name %> <%= @contact.last_name %>
</p>
<p><strong>Email:</strong>
<%= @contact.email %>
</p>
<p><strong>Message:</strong></p>
<p><%= @contact.message %></p>
User confirmation: app/views/contact_mailer/confirmation_email.html.erb
<h1>Thanks for your message, <%= @contact.first_name %>!</h1>
<p>We've received your message and will get back to you as soon as possible.</p>
<p>Here's a copy of what you sent:</p>
<p><%= @contact.message %></p>
These templates use the instance variable @contact set in the mailer to render the user information and message body inside the email.
6. Handle form submission in the controller
With the model, form, and mailer in place, connect everything inside ContactsController#create so that saving a contact also triggers two emails and shows alerts on the website.
In app/controllers/contacts_controller.rb:
class ContactsController < ApplicationController
def create
@contact = Contact.new(contact_params)
if @contact.save
# email to you
ContactMailer.with(contact: @contact).received_email.deliver_now
# confirmation email to the user
ContactMailer.with(contact: @contact).confirmation_email.deliver_now
redirect_to root_path(anchor: 'contact'),
notice: 'Message sent! Check your email for confirmation.'
else
redirect_to root_path(anchor: 'contact'),
alert: 'Message failed to send!'
end
end
private
def contact_params
params.require(:contact)
.permit(:first_name, :last_name, :email, :message)
end
end
Here is what happens in the create action:
Contact.new(contact_params)builds a new contact from the submitted form parameters while enforcing strong parameters.if @contact.saveruns validations and writes the record to the database only if everything is valid.ContactMailer.with(contact: @contact).received_email.deliver_nowbuilds and sends the admin notification email immediately.ContactMailer.with(contact: @contact).confirmation_email.deliver_nowsends a confirmation email directly to the person who filled in the form.- The redirect back to
root_pathwith an anchor and flash message shows an on‑page alert near the contact section, so the user gets immediate feedback even before checking their inbox.
7. Configure SMTP in production
To send real emails in production, configure Action Mailer with your SMTP provider. The exact settings depend on which service you use, but the structure is always the same: a delivery method and an smtp_settings hash.
A typical config/environments/production.rb setup looks like this:
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.yourprovider.com',
port: 587,
domain: 'yourdomain.com',
user_name: ENV['SMTP_USERNAME'],
password: ENV['SMTP_PASSWORD'],
authentication: 'plain',
enable_starttls_auto: true
}
config.action_mailer.default_url_options = { host: 'yourdomain.com' }
Environment variables such as SMTP_USERNAME and SMTP_PASSWORD keep your credentials out of version control. On platforms like Heroku you can set them using commands such as heroku config:set SMTP_USERNAME=hello@yourdomain.com and heroku config:set SMTP_PASSWORD=your-smtp-token.
8. Preview emails easily in development
While developing your contact form, it is often more convenient to preview emails in the browser instead of sending them. A gem like letter_opener intercepts outgoing emails in development and opens them in a new tab instead of delivering to an inbox.
In your Gemfile:
group :development do
gem 'letter_opener'
end
Then in config/environments/development.rb:
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
Whenever you submit the contact form locally, Rails will save the contact and open both the admin notification and confirmation email in your browser so you can verify the layout, copy, and links without sending external mail.
With this setup you now have a fully functional Ruby on Rails contact form powered by Action Mailer. Visitors see clear alerts on your site, receive a confirmation email in their inbox, and you get a clean, structured notification every time someone reaches out through your portfolio or Rails app.