[フレーム]
Last Updated: October 31, 2023
·
36.39K
· tiagorg

How to create a contact form for a GitHub Pages served Jekyll website

I have recently put together a Jekyll website hosted in GitHub Pages here: tgarcia.com.br

As you probably know, GitHub Pages serve static-only websites (which I will refer as front-end), which means there is no available back-end to create things such as contact form.

So firstly I was just giving my email address on the website, which turned out to be BAD IDEA. Thinking better I decided I needed a contact form with a Captcha challenge - and for that I need a back-end to send this email.

It didn't take long for me to find the post Create a contact form for Jekyll, which is awesome, however it describes a solution for sites that serve both the front-end and back-end on the same node.

Which differs from my case, once I have the front-end being served on GitHub pages, and the back-end must be served somewhere else (in my case I use Heroku).

Well, after a weekend of work I was able to put together a solution to contemplate my scenario and here I describe it. I had to add CORS requests handling and I improve the workflow by doing all the validation on the same request, without using sessions to make it more secure and faster.

  • Sign up for a Heroku free account if you don't have an account already.
  • Create an app on Heroku and pick a meaningful name for you (mine is tgarcia-contact-form), otherwise Heroku will generate a random one.
  • Sign up for a Sendgrid Starter plan on your app.
  • Sign up for a reCAPTCHA account and register your Heroku back-end (mine is tgarcia-contact-form.herokuapp.com). IMPORTANT: select the option to generate it as a GLOBAL KEY, otherwise your GitHub Pages front-end won't work with reCAPTCHA.
  • Create a local git repo for a Rack-based application and add the following files:

Gemfile

source 'https://rubygems.org'
ruby '2.0.0'
gem 'rack'
gem 'rack-recaptcha'
gem 'sinatra'
gem 'pony'
gem 'json'

config.ru

require 'rubygems'
require 'sinatra'
require 'json'
require 'rack/recaptcha'
require 'pony'

use Rack::Recaptcha, :public_key => 'YOUR_PUBLIC_KEY_FROM_RECAPTCHA', :private_key => YOUR_PRIVATE_KEY_FROM_RECAPTCHA'
helpers Rack::Recaptcha::Helpers

require './application'
run Sinatra::Application

Please insert your reCAPTCHA's public and private keys where specified.

application.rb

before do
 content_type :json
 headers 'Access-Control-Allow-Origin' => '*',
 'Access-Control-Allow-Methods' => ['POST']
end

set :protection, false
set :public_dir, Proc.new { File.join(root, "_site") }

post '/send_email' do
 if recaptcha_valid?
 res = Pony.mail(
 :from => params[:name] + "<" + params[:email] + ">",
 :to => 'YOUR_EMAIL_ADDRESS',
 :subject => "[YOUR FILTER] " + params[:subject],
 :body => params[:message],
 :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_email' }.to_json
 end
 else
 { :message => 'failure_captcha' }.to_json
 end
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

Please insert your email address and a filter for the email subject.

  • Now you need to add some client-side code that will go to your Jekyll site at GitHub Pages:
function showRecaptcha(element) {
 Recaptcha.create('YOUR_PUBLIC_KEY_FROM_RECAPTCHA', element, {
 theme: 'custom', // you can pick another at https://developers.google.com/recaptcha/docs/customization
 custom_theme_widget: 'recaptcha_widget'
 });
}

function setupRecaptcha() {
 var contactFormHost = 'YOUR_BACKEND_ADDRESS_FROM_HEROKU',
 form = $('#contact-form'),
 notice = form.find('#notice');

 if (form.length) {
 showRecaptcha('recaptcha_widget');

 form.submit(function(ev){
 ev.preventDefault();

 $.ajax({
 type: 'POST',
 url: contactFormHost + 'send_email',
 data: form.serialize(),
 dataType: 'json',
 success: function(response) {
 switch (response.message) {
 case 'success':
 form.fadeOut(function() {
 form.html('<h4>' + form.data('success') + '</h4>').fadeIn();
 });
 break;

 case 'failure_captcha':
 showRecaptcha('recaptcha_widget');
 notice.text(notice.data('captcha-failed')).fadeIn();
 break;

 case 'failure_email':
 notice.text(notice.data('error')).fadeIn();
 }
 },
 error: function(xhr, ajaxOptions, thrownError) {
 notice.text(notice.data('error')).fadeIn();
 }
 });
 });
 }
}

And here is my HTML form:

<form id="contact-form" class="contact-form" method="post" data-success="Message successfully sent!">

 <label for="name">Name</label>
 <input id="name" type="text" name="name" class="field" required autofocus /><br/>

 <label for="email">E-mail</label>
 <input id="email" type="email" name="email" class="field" required /><br/>

 <label for="subject">Subject</label>
 <input id="subject" type="text" name="subject" class="field" required /><br/>

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

 <label for="recaptcha_response_field">Captcha</label>
 <div id="recaptcha_widget" class="recaptcha">
 <div class="image">
 <div id="recaptcha_image"></div>
 </div>

 <div class="headline recaptcha_only_if_image">Enter the words above:</div>
 <div class="headline recaptcha_only_if_audio">Enter the numbers you hear:</div>

 <input type="text" id="recaptcha_response_field" name="recaptcha_response_field" required />

 <span class="recaptcha_icon"><a href="javascript:Recaptcha.reload()"><i class="fa fa-refresh"></i></a></span>
 <span class="recaptcha_icon recaptcha_only_if_image"><a href="javascript:Recaptcha.switch_type('audio')"><i class="fa fa-volume-up"></i></a></span>
 <span class="recaptcha_icon recaptcha_only_if_audio"><a href="javascript:Recaptcha.switch_type('image')"><i class="fa fa-font"></i></a></span>
 <span class="recaptcha_icon"><a href="javascript:Recaptcha.showhelp()"><i class="fa fa-question-circle"></i></a></span>
 </div><br/>
 <div id="notice" class="notice" data-captcha-failed="Incorrect captcha!" data-error="There was an error sending the message, please try again."></div>
 <button type="submit">Send</button>
</form>

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

BTW I am using Font Awesome here.

And should you want to see my styles (I used LESS here)

.contact-form {
 h4 {
 color: #668944;
 margin-left: 20px;
 }
 label {
 width: 100px;
 margin: 5px 0;
 display: inline-block;
 vertical-align: top;
 }
 input, textarea, .recaptcha, button {
 margin: 5px 0;
 padding: 5px;
 -webkit-border-radius: 5px;
 -moz-border-radius: 5px;
 border-radius: 5px;
 border: 1px solid #bbb;

 &:focus {
 outline-color: @accent-color;
 }
 }
 input.field, textarea {
 min-width: 330px;
 }
 input[name=subject], textarea {
 width: 73%;
 }
 textarea {
 height: 150px;
 }
 button {
 border: 0;
 background-color: @accent-color;
 padding: 8px 20px;
 color: #fff;
 margin-top: 20px;

 &:hover {
 background-color: darken(@accent-color, 10%);
 color: darken(#fff, 10%);
 }
 }
 .notice {
 display: none;
 background-color: #f2dede;
 border: 1px solid #ebccd1;
 -webkit-border-radius: 5px;
 -moz-border-radius: 5px;
 border-radius: 5px;
 padding: 10px 15px;
 color: #a94442;
 margin-top: 15px;
 }
 .recaptcha {
 padding: 0;
 display: inline-block;

 div.image {
 -webkit-border-radius: 5px 5px 0 0;
 -moz-border-radius: 5px 5px 0 0;
 border-radius: 5px 5px 0 0;
 padding: 10px;
 background-color: #fff;
 width: 320px;

 br {
 display: none;
 }
 embed, span {
 float: left;
 clear: both;
 a {
 cursor: pointer;
 font-size: 0.9em;
 }
 }
 }

 div.headline {
 padding: 5px 5px 0 10px;
 }

 input[type=text] {
 margin: 10px;
 }
 }
}

In here, @accent-color is my default link color.

I hope it serves you well!

6 Responses
Add your response

config.ru</code> or config.rb</code>?

over 1 year ago ·

https://formkeep.com is also designed to be drop-in, super-simple forms for Jekyll.

over 1 year ago ·

I think you're missing a quote here at the start of YOUR_PRIVATE...:

:privatekey => YOURPRIVATEKEYFROM_RECAPTCHA'

over 1 year ago ·

I'd suggest to use something like http://www.kvstore.io. It's a key/value pair storage service that supports the "client-side only environments" like static websites.

It's super easy to use it and gives you the strength to manage your data with a full set of RESTful API... I just posted an article on github/jekyll or whatever integration with kvstore.io... : https://medium.com/ @lordkada/store-user-generated-content-from-jekyll-github-pages-or-similar-ded76aabf17#.aqjafjiez

Enjoy

Just my 2 cents..

DISCLAIMER: I'm the author of kvstore.io ;-)

over 1 year ago ·

Can you link URL on your result git-page with form working over 'heroku'?

over 1 year ago ·

You can also use getform.org It's great for static websites.

over 1 year ago ·

AltStyle によって変換されたページ (->オリジナル) /