TommyBlue.it

Create a contact form for Jekyll

As I promised to a reader who wrote me an email some days ago, with this post I’ll explain how I built the contact form of this website using Sinatra and Sendgrid. As you know (or you’re just learning) this site is made by Jekyll, a static site generator written in Ruby. As the result of the work of Jekyll it is a static HTML website, so it’s not immediate to build a contact form. I tried to find a JS solution to maintain a full static website, but didn’t find any. So I wrote a few Ruby lines and the contact form is now working.

How to let the form work is strictly dependent on how you deploy your Jekyll website. I use Heroku so I deploy the site as a Rack-based app using Sinatra, this how-to works only if you have my deployment configuration. If not, you must adapt it to your needs.

The first step is to register a free account to reCAPTCHA and get the public and private API keys. Then register a Sendgrid free account as Heroku add-on in your website app.

To use a rack-based app on Heroku you need a config.ru file, this is mine:


require 'rubygems'
require 'sinatra'
require 'rack/recaptcha'

use Rack::Recaptcha, :public_key => 'MyPublicKey', :private_key => 'TheS3cr3tS3cr3tKey'
helpers Rack::Recaptcha::Helpers
enable :sessions

require './application'
run Sinatra::Application

Just insert the reCAPTCHA keys and the file is ready. Now the application.rb file called by the require above:


set :public, Proc.new { File.join(root, "_site") }

post '/send' do
  if recaptcha_valid?
    session[:captcha] = true
    { :message => 'success' }.to_json
  else
    session[:captcha] = false
    { :message => 'failure' }.to_json
  end
end

post '/send_email' do
    require 'pony'
    require 'json'

    if session[:captcha]
      session[:captcha] = false
      res = Pony.mail(
	:from => params[:name] + "<" + params[:email] + ">",
	:to => 'me@mydomain.com',
	:subject => "Message from your awesome website :)",
	:body => params[:message],
	:port => '587',
	:via => :smtp,
	:via_options => {
	  :address              => 'smtp.sendgrid.net',
	  :port                 => '587',
	  :enable_starttls_auto => true,
	  :user_name            => ENV['SENDGRID_USERNAME'],
	  :password             => ENV['SENDGRID_PASSWORD'],
	  :authentication       => :plain,
	  :domain               => 'heroku.com'
	})
      content_type :json
      if res
	  { :message => 'success' }.to_json
      else
	  { :message => 'failure' }.to_json
      end
    else
      { :message => 'failure' }.to_json
    end
end

before do
    response.headers['Cache-Control'] = 'public, max-age=36000'
end

not_found do
    File.read('_site/404.html')
end

get '/*' do
    file_name = "_site#{request.path_info}/index.html".gsub(%r{\/+},'/')
    if File.exists?(file_name)
	File.read(file_name)
    else
	raise Sinatra::NotFound
    end
end

As you see in the code I use two methods, send and send_email: the first check the captcha and set a session variable, returning a JSON message (success). The second method sends the email using Pony only if the captcha was verified. The SendGrid username and password are loaded automatically from your Heroku environment.

The last step is to create the contact form page, including the reCAPTCHA js:


<script type="text/javascript" src="http://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>

<script type="text/javascript">
  function showRecaptcha(element) {
     Recaptcha.create("MyPublicKey", element, {
       theme: "red",
       callback: Recaptcha.focus_response_field});
   }
   $(document).ready(function(){
	showRecaptcha('recaptcha_div');

	$("#form").submit(function(ev){
	    ev.preventDefault();
	    if (!$(this).valid()) return;
	    $.ajax({
	      type: "post",
	      url: "/send",
	      data: $('#form').serialize(),
	      dataType: "json",
	      success: function(response) {
		if(response.message === "success") {
		  $.ajax({
		      type: "post",
		      url: "/send_email",
		      data: $('#form').serialize(),
		      dataType: "json",
		      success: function(response) {
			  $('#form').html("<div id='message'></div>");
			  if(response.message === "success") {
			      $('#message').html("<h2>Message successfully sent.</h2>").hide().fadeIn(1500);
			  } else {
			      $('#message').html("<h2>Error sending the message</h2>").hide().fadeIn(1500);
			  }
		      },
		      error: function(xhr, ajaxOptions, thrownError){
			  $('#form').html("<div id='message'></div>");
			  $('#message').html("<h2>Error sending the message</h2>").hide().fadeIn(1500);
		      }
		  });
		} else {
		  showRecaptcha('recaptcha_div');
		  $('#notice').html("Captcha failed!").hide().fadeIn(1500);
		}
	      },
	      error: function(xhr, ajaxOptions, thrownError){
		  $('#form').html("<div id='message'></div>");
		  $('#message').html("<h2>Error sending the message</h2>").hide().fadeIn(1500);
	      }
	    });
	});
    });
</script>

The code seems a bit tricky :) but it’s simple. It just intercepts the form submission, send a first POST call to /send and, if the captcha is verified, generates a second POST call to /send_email, which sends the email. The last piece is the form HTML code:


<form id="form" method="post">
	<label for="name">Name</label>
	<input type="text" name="name" id="name" />

	<label for="email">Email</label>
	<input type="text" name="email" id="email" />

	<label for="message" class="label">Message</label>
	<textarea name="message" id="message"></textarea>

	<div id="recaptcha_div"></div>
	<div id="notice"></div>

	<input class="submit" type="submit" value="Send" />
</form>

That’s it, now you can send email from a static website.