Skip to content

Instantly share code, notes, and snippets.

@stevecondylios
Last active April 2, 2024 15:50
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save stevecondylios/16a53b73f22621e3cde2e17096dbf5ca to your computer and use it in GitHub Desktop.
Save stevecondylios/16a53b73f22621e3cde2e17096dbf5ca to your computer and use it in GitHub Desktop.
Create a Contact Form in Rails 6

How to make a contact form in rails 6

This is a quick walk through on how to:

  1. make a contact form in rails 6,
  2. test it locally, and
  3. move it into production using heroku and the MailGun addon

This uses the free heroku and mailgun plans. If you get stuck on any part, check the full code here.

Make app and setup locally

Create a new rails app (we don't need a database for this, so skip that option if you prefer)

rails new contactforminrails6 --database=postgresql

cd into your newly create rails application with cd contactforminrails6.

Add the mail_form gem to Gemfile and bundle install to install it.

# Gemfile
gem 'mail_form'

If you used the database option when creating the rails app, run rake db:create db:migrate to create and migrate it. If you didn't use that option, ignore this step.

Add routes

Let's add the new and create routes. After doing so, your routes.rb file should look like this:

Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  resources :contacts, only: [:new, :create]
end

Generate a controller

Generate a new controller with

rails g controller contacts

Add this to app/controllers/contacts_controller.rb:

  def new
    @contact = Contact.new
  end

  def create
    @contact = Contact.new(params[:contact])
    @contact.request = request
    if @contact.deliver
      flash.now[:success] = 'Message sent!'
    else
      flash.now[:error] = 'Could not send message'
      render :new
    end
  end

Add a model

Create a file called app/models/contact.rb and place the following in it (changing "your.email@address.com" to the address you wish for the form to send to):

class Contact < MailForm::Base
  attribute :name,      validate: true
  attribute :email,     validate: /\A([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})\z/i
  attribute :message
  attribute :nickname,  captcha: true

  # Declare the e-mail headers. It accepts anything the mail method
  # in ActionMailer accepts.
  def headers
    {
      :subject => "Contact Form Inquiry",
      :to => "your.email@address.com",
      :from => %("#{name}" <#{email}>)
    }
  end
end

In the above, I haven't validated the presence of a message, but if you prefer to, simply add validate: true in the same way it's been done for the :name field.

You may have noticed the nickname field. That's a bit odd, isn't it? It's from the mail_form gem docs, and it's purpose is to trick bots! We'll make that a field hidden in the view, so no human would submit to it, so if we receive a value in that field, we'll conclude that it's a bot, and ignore the request!

Add views

Let's create our contact form in app/views/contacts/new.html.erb:

<div class="container">
  <h1>Contact Form</h1>
  <%= form_for @contact do |f| %>
    <div class="col-md-6">
      <%= f.label :name %></br>
      <%= f.text_field  :name, required: true, class: "contact-form-text-area" %></br>

      <%= f.label :email %></br>
      <%= f.text_field :email, required: true, class: "contact-form-text-area" %></br>

      <%= f.label :message %></br>
      <%= f.text_area :message, rows: 8, cols: 40, required: true, class: "contact-form-text-area", 
            placeholder: "Send me a message"%></br>

      <div class= "d-none">
        <%= f.label :nickname %>
        <%= f.text_field :nickname, :hint => 'Leave this field blank!' %>
      </div>

      <%= f.submit 'Send Message', class: 'btn btn-primary' %>
    </div>
  <% end %>
</div>

and a simple confirmation message in app/views/contacts/create.html.erb

<div class="container">
  <h1>Contact Form</h1>

  <h3>Thank you for your message!</h3>
</div>

lastly, some informative flash messages, by adding this to app/views/layouts/application.html.erb between <body> and <%= yield %>:

<div class="container">
  <br>
  <% flash.each do |key, message| %>
    <p class="alert alert-<%= key %>"><%= message %></p>
  <% end %>
</div>

Add styles

The only critical thing to do is ensure that the nickname field isn't displayed to humans. You can simply delete it from your form view if preferred. However, another way to do this is to use some CSS to hide it. Here, I'll add bootstrap to the rails app and use the d-none class we gave that form field will hide it for us!

Run this to install bootstrap bin/yarn add bootstrap jquery

Then add this to app/javascript/packs/application.js:

window.$ = window.jQuery = require("jquery");
import "bootstrap/dist/js/bootstrap.bundle.js"

Lastly, add this to app/assets/stylesheets/application.scss. If that file doesn't exist, you can rename app/assets/stylesheets/application.css. Then add this:

//= require bootstrap/dist/css/bootstrap

Restart your rails server.

Note: if you prefer not to use bootstrap, that's okay, simply add a CSS class (e.g. 'hidden') to the nickname field in the form, and define it in app/assets/stylesheets/application.scss like so:

.hidden {
  display: none;
 }

Testing locally

At this point, we should have a fully functional contact form in our local environment. You can confirm this by submitting the form (leaving the nickname field blank), and checking the server logs to ensure the mail is sent.

I like to do a little more testing locally, just to ensure everything works as expected. For this, I use mailcatcher. Install it with gem install mailcatcher.

Add these to config/environments/development.rb:

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { :address => '127.0.0.1', :port => 1025 }
config.action_mailer.raise_delivery_errors = false

Now another terminal window and run the simple command mailcatcher to start it, then pasting this http://127.0.0.1:1080/ into your browser to show a virtual inbox with all your outgoing mail! Note: you'll need to restart your rails server for this to take effect.

Try submitting your contact form again to see the outbound mail appear in mailcatcher. Once that works, it's time to take this functional form on to the web!

Moving into production

Create a heroku app

If you don't already have the heroku CLI, you'll need to install and login to the heroku CLI.

Create a new heroku app with heroku create <name of new app>. I used the same name as the rails app e.g. heroku create contactforminrails6, but you don't have to. Check the console output to confirm that heroku created your new app, it will give you the app URL, e.g. https://contactforminrails6.herokuapp.com.

Move rails app to heroku

First we must move the app code to GitHub. In the browser, head to GitHub, and create a new repository.

In the terminal, cd into the root directory of your app and run:

git init
git add . 
git commit -m "First commit to github repo"
git remote add origin https://github.com/username/name_of_new_repository.git
git push -u origin master

Be sure to replace https://github.com/username/neme_of_new_repository.git in the above with the URL of your new repository.

Now your rails app will be on GitHub, but not yet on heroku. But pushing it to heroku is easy:

git push heroku master

You should see console output indicating it's building. Wait a few minutes while heroku builds and deploys your app.

Configure production app to use Mailgun

Once your app is build and it has deployed, you can visit it at the URL heroku provided you when you created it. E.g. https://contactforminrails6.herokuapp.com/contacts/new. You now have a working app on heroku! If you see a 'page not found' message, be sure you appended /contacts/new on the URL (the root URL doesn't have routes defined for it yet, so it's expected that there should not be any page found for it).

We still need to configure the mailer to work in production. To do this, we'll use the heroku Mailgun addon. Simply run this:

heroku addons:create mailgun:starter

And configure our app to use Mailgun in production by adding the following inside config/environments/production.rb:

ActionMailer::Base.smtp_settings = {
  :port           => ENV['MAILGUN_SMTP_PORT'],
  :address        => ENV['MAILGUN_SMTP_SERVER'],
  :user_name      => ENV['MAILGUN_SMTP_LOGIN'],
  :password       => ENV['MAILGUN_SMTP_PASSWORD'],
  :domain         => 'yourapp.heroku.com', # UPDATE THIS VALUE WITH YOUR OWN APP
  :authentication => :plain,
}
ActionMailer::Base.delivery_method = :smtp

In the above, remember to change yourapp.heroku.com to the name of your app, leave everything else as-is.

Now commit this to git and push to heroku with:

git add . 
git commit -m "Updating production mailer settings to use MailGun"
git push 
git push heroku master

That will push your updated code to GitHub and then build it on heroku.

Final test

Once that's done, head to your conctact form on your heroku app. Mine's at https://contactforminrails6.herokuapp.com/contacts/new and test your form!

You won't receive any mail just yet. If you check your heroku logs with heroku logs -n 500, you'll see something like:

Net::SMTPUnknownError (could not get 3xx (421: 421 Domain sandbox8f9918227df9s7b6s8b9s73503.mailgun.org is not allowed to send: Sandbox subdomains are for test purposes only. Please add your own domain or add the address to authorized recipients in Account Settings.

This is easy to fix. In the browser, go to https://dashboard.heroku.com/apps/contactforminrails6 (replacing contactforminrails6 with the name of your app). Simply click on the Mailgun icon - doing so will activate your Mailgun addon:

Give your contact form a try, wait a few seconds and check your inbox!

Congratulations

Congratulations - you just built a fully functional contact form in rails 6 and deployed it to production!

Useful resources

  • The completed form can be found here
  • Full code for this tutorial can be found here
  • This video tutorial is very useful
  • As is this tutorial
  • This tutorial uses an outdated method (sending via Gmail), but may still be useful.
@pglombardo
Copy link

pglombardo commented Sep 30, 2021

Thank you - very helpful. End result here: https://pwpush.com/en/feedbacks/new

@LightningRpper
Copy link

How do you provide a link to a page like this if you're implementing a page like this to an entire website with other pages? Thank you in advance.

@LightningRpper
Copy link

I'm trying to get it as a part of a website I'm currently making and I'm able to get to the "new" webpage and type a message. But when I hit "Submit Message" it tries to take me to a webpage called "contacts" which doesn't exist. It also says "uninitialized constant ContactsController Did you mean? ContactController" even though I do not have "ContactsController" anywhere". What do I do?

@pglombardo
Copy link

How do you provide a link to a page like this if you're implementing a page like this to an entire website with other pages? Thank you in advance.

That is my staging site which is part of a CI pipeline where code goes through a series of points and validations before being deployed to the production site.

But when I hit "Submit Message" it tries to take me to a webpage called "contacts" which doesn't exist. It also says "uninitialized constant ContactsController Did you mean? ContactController" even though I do not have "ContactsController" anywhere". What do I do?

Are you sure you followed the instructions correctly? This section above creates the ContactsController controller that you should have.

It might be worth double checking the steps you took.

@LightningRpper
Copy link

Are you sure you followed the instructions correctly? This section above creates the ContactsController controller that you should have.

It might be worth double checking the steps you took.

When I made the controller, I chose to name it ContactController believing it would not make a difference. But I don't see how it leads to this particular error.

@pglombardo
Copy link

pglombardo commented Feb 14, 2022

Rails has conventions and assumptions on naming (and plurality). This is one of them. Fix that controller name and you should get a bit further.

@LightningRpper
Copy link

Rails has conventions and assumptions on naming (and plurality). This is one of them. Fix that controller name and you should get a bit further.

Redid some of the work and now the webpages work just fine. Still need to fully get those messages through though.

@John35961
Copy link

Works juste fine! Thanks a lot for the clear explanations

@david-lafontant
Copy link

Unfortunately, I can give you more than one star. Thank you very much for your clear and concise explanations.

@LightningRpper
Copy link

Anyone know how I can modify this controller to get the message to go to an icloud email address (me.com) instead of mailcatcher?

@MaxGueyeMax
Copy link

Hello everyone, thanks for all the details! Very helpful, I would just add one thing: once your production is configured do not forget to register your email into Mailgun, otherwise it will only work locally and not in production. You go to Mailgun > Sending > Overview and on your left > Authorized Recipients
Good day to all

@rebuilt
Copy link

rebuilt commented Nov 2, 2022

Hello, thanks for this tutorial. It helped me get the contact form up and running. One issue I ran into because I'm using rails 7 is that the flash messages don't show up. The server logs reported "Processing by ContactsController#create as TURBO_STREAM" I fixed it by making sure the form doesn't use turbo streams when submitting:

<%= form_with url: some_path, data: { turbo: false } do |f| %>
   ...
<% end %>

@MrBabon
Copy link

MrBabon commented Aug 14, 2023

thanks

@grgatzby
Copy link

grgatzby commented Nov 7, 2023

Thank you from this great tutorial!

@MaxGueyeMax thanks too for the helpful tip of setting the authorised recipient address in MailGun, but for me I had to fill it on the RIGHT of my screen, maybe they changed the layout!
Cheers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment