Skip to content

Commit

Permalink
Nicer signup and password emails
Browse files Browse the repository at this point in the history
Prettify and rework the emails sent by devise, including signup
confirmation, password reset, and a few others. Include some
branding and improve the text.

Up until now we were using the standard default devise emails which
are very generic and not very nice.

Introduce the bootstrap-mailer gem for generating email client
compatible html because that is very hard to do apparently. We'll
haml instead of erb for consistency and since that's how we like to
do it.

Also improve the after_registration page, and include some
troubleshooting advice on it.

Closes #3
  • Loading branch information
simonbaird committed Jun 29, 2022
1 parent 6ea7d6e commit 3d64ba0
Show file tree
Hide file tree
Showing 27 changed files with 318 additions and 34 deletions.
3 changes: 3 additions & 0 deletions rails/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ gem 'haml-rails'
# Support S3 for ActiveStorage
gem "aws-sdk-s3", require: false

# For nice emails
gem 'bootstrap-email'

# For payments
gem 'pay', '~> 3.0'
gem 'stripe', '>= 2.8', '< 6.0'
Expand Down
14 changes: 14 additions & 0 deletions rails/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ GEM
aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.18)
bindex (0.8.1)
bootstrap-email (1.2.0)
htmlbeautifier (~> 1.3)
nokogiri (~> 1.6)
premailer (~> 1.7)
sassc (~> 2.1)
builder (3.2.4)
byebug (11.1.3)
capybara (3.37.1)
Expand All @@ -96,6 +101,8 @@ GEM
childprocess (4.1.0)
concurrent-ruby (1.1.10)
crass (1.0.6)
css_parser (1.11.0)
addressable
dalli (3.2.2)
devise (4.8.1)
bcrypt (~> 3.0)
Expand Down Expand Up @@ -125,6 +132,8 @@ GEM
haml (>= 4.0, < 6)
nokogiri (>= 1.6.0)
ruby_parser (~> 3.5)
htmlbeautifier (1.4.2)
htmlentities (4.3.4)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
jbuilder (2.11.5)
Expand Down Expand Up @@ -162,6 +171,10 @@ GEM
pay (3.0.24)
rails (>= 6.0.0)
pg (1.4.1)
premailer (1.16.0)
addressable
css_parser (>= 1.6.0)
htmlentities (>= 4.0.0)
public_suffix (4.0.7)
puma (5.6.4)
nio4r (~> 2.0)
Expand Down Expand Up @@ -269,6 +282,7 @@ PLATFORMS
DEPENDENCIES
acts-as-taggable-on (~> 7.0)
aws-sdk-s3
bootstrap-email
byebug
capybara (>= 3.26)
dalli
Expand Down
Binary file added rails/app/assets/images/email-banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions rails/app/mailers/devise_bootstrap_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#
# See also test/mailers/previews/devise_bootstrap_mailer_preview
# Based on https://github.com/bootstrap-email/bootstrap-email/issues/41
#
class DeviseBootstrapMailer < Devise::Mailer

layout 'bootstrap-mailer'
default template_path: 'devise/mailer'

def devise_mail(record, action, opts = {}, &block)
initialize_from_record(record)

@email_title = email_title_for(action)

# Use bootstrap mail
make_bootstrap_mail(headers_for(action, opts.merge(to: record.pretty_email)), &block)
end

private

# See docker/bundle/ruby/3.1.0/gems/devise-4.8.1/lib/devise/mailers/helpers.rb
# (IIUC the more correct way to change the email subject wording would be to
# create an I18n locale file but let's save that for another day.)

# Save the method from the base class so we can use it below
alias_method :orig_subject_for, :subject_for

def email_title_for(action)
orig_subject_for(action).
# Tweak the defaults a little
sub(/Changed$/i, "change notification").
sub(/^Confirmation/i, "Email confirmation").
sub(/^Reset password/i, "Password reset").
sub(/instructions$/i, "")
end

def subject_for(action)
"Tiddlyhost #{email_title_for(action).downcase}"
end

end
12 changes: 12 additions & 0 deletions rails/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ def username_or_email
username.presence || email
end

def short_name
name.split(/\s+/).first
end

def pretty_email
%{"#{name}" <#{email}>}
end

def email_change_in_progress?
unconfirmed_email.present?
end

scope :with_plan, ->(*plan_names) { where( plan_id: Plan.where(name: plan_names.map(&:to_s)).pluck(:id)) }
scope :without_plan, ->(*plan_names) { where.not(plan_id: Plan.where(name: plan_names.map(&:to_s)).pluck(:id)) }

Expand Down
2 changes: 1 addition & 1 deletion rails/app/views/devise/confirmations/new.html.haml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
=render layout: 'form_wrapper', locals: { heading: 'Resend confirmation instructions',
intro_text: "Enter your email address to resend the password confirmation link." } do
intro_text: "Enter your email address to resend the email confirmation link." } do

=form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f|

Expand Down
2 changes: 2 additions & 0 deletions rails/app/views/devise/mailer/_button_link.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
%p.pt-2
=link_to link_text, link_url, class: "btn btn-success", style: "font-weight: bold;"
4 changes: 4 additions & 0 deletions rails/app/views/devise/mailer/_salutation.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%p
Hi #{@resource.short_name},


3 changes: 3 additions & 0 deletions rails/app/views/devise/mailer/_show_email.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
%p.pt-4
%small
Your Tiddlyhost account email address is <b>#{@resource.email}</b>.

This file was deleted.

28 changes: 28 additions & 0 deletions rails/app/views/devise/mailer/confirmation_instructions.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=render 'salutation'

-if @resource.email_change_in_progress?
-# Actually we're confirming an email address change
%p
To verify the change to your Tiddlyhost account email address
please click 'Confirm email' below.

=render 'button_link', link_text: 'Confirm email', link_url: confirmation_url(@resource, confirmation_token: @token)

-else
-# It's a new signup
%p
Welcome to Tiddlyhost!

%p
To complete the signup process please confirm your
email address by clicking 'Confirm account' below.

=render 'button_link', link_text: 'Confirm account', link_url: confirmation_url(@resource, confirmation_token: @token)

=render 'show_email'

%p.pt-5
%small.text-gray-600
You received this email because you or someone else submitted the
=link_to 'Tiddlyhost sign up', "#{Settings.main_site_url}/users/sign_up", target: "_blank"
form using this email address.
7 changes: 0 additions & 7 deletions rails/app/views/devise/mailer/email_changed.html.erb

This file was deleted.

15 changes: 15 additions & 0 deletions rails/app/views/devise/mailer/email_changed.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-#
-# Possibly unused..?
-#
-if @resource&.unconfirmed_email?
%p
This is a notification to inform you that your Tiddlyhost account email address
is being changed from <b>#{@resource.email}</b> to <b>#{@resource.unconfirmed_email}</b>.

%p
A confirmation from the new email address is required to verify the change.

-else
%p
This is a notification to inform you that that your Tiddlyhost account email address
has been successfully changed to <b>#{@resource.email}</b>.
3 changes: 0 additions & 3 deletions rails/app/views/devise/mailer/password_change.html.erb

This file was deleted.

6 changes: 6 additions & 0 deletions rails/app/views/devise/mailer/password_change.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-#
-# Possibly unused..?
-#
%p
This is a notification to inform you that that your Tiddlyhost account
password has been successfully changed.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
=render 'salutation'

%p
You can set a new Tiddlyhost account password by clicking 'Change password' below.

=render 'button_link', link_text: 'Change password', link_url: edit_password_url(@resource, reset_password_token: @token)

=render 'show_email'

%p.pt-4
%small
If you didn't request a password reset please ignore this email.

%p.pt-5
%small.text-gray-600
You received this email because you or someone else submitted the
=link_to 'Forgot password?', "#{Settings.main_site_url}/users/password/new", target: "_blank"
form using this email address.
7 changes: 0 additions & 7 deletions rails/app/views/devise/mailer/unlock_instructions.html.erb

This file was deleted.

14 changes: 14 additions & 0 deletions rails/app/views/devise/mailer/unlock_instructions.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-#
-# Possibly unused..?
-#
=render 'salutation'

%p
Your Tiddlyhost account has been locked due to an excessive number of unsuccessful login attempts.

%p
To unlock your account click the 'Unlock account' link below.

=render 'button_link', link_text: 'Unlock account', link_url: unlock_url(@resource, unlock_token: @token)

=render 'show_email'

This file was deleted.

28 changes: 28 additions & 0 deletions rails/app/views/devise/registrations/after_registration.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=render layout: 'form_wrapper',
locals: { heading: "#{bi_icon('check-circle', fill: 'green', style: 'opacity: 85%; font-size: 65%;')}Account created".html_safe } do

%p.pt-4
Thanks for signing up!

%p.pb-5
A confirmation email has been sent to your email address.
To complete the signup process please click the confirmation link
in that email.

%hr.mt-5.mb-4

%h6.text-muted Troubleshooting

%ul.pt-2.pe-2
%li.text-muted.pb-3.small
If the confirmation email did not arrive it may be being classified as spam.
Check your spam folder or try adding 'tiddlyhost@gmail.com' to your email
contacts and then use
=link_to 'this form', new_confirmation_path(:user)
to resend the email.

%li.text-muted.small
If you're still unable to receive the confirmation link,
=mail_to Settings.support_email, 'email me', subject: "Tiddlyhost signup problems",
body: "I signed up and I didn't receive my confirmation email.\n\nPlease send it to me manually if you can."
and I'll see if I can send it to you directly.
1 change: 1 addition & 0 deletions rails/app/views/devise/registrations/edit.html.haml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

=render layout: 'form_wrapper', locals: { heading: 'Update account' } do

=form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f|
Expand Down
41 changes: 41 additions & 0 deletions rails/app/views/layouts/bootstrap-mailer.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<%#
#
# This will be converted into email friendly html with tables and inlined styles.
# The email content is defined under app/views/devise/mailer.
#
# See also:
# - https://bootstrapemail.com/
# - app/mailers/devise_bootstrap_mailer.rb
# - test/mailers/previews/devise_bootstrap_mailer_preview.rb
# - http://tiddlyhost.local:3333/rails/mailers/devise_bootstrap_mailer
# - or https://tiddlyhost.local/rails/mailers/devise_bootstrap_mailer
#
-%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="x-apple-disable-message-reformatting">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
</head>
<body class="bg-light">
<div class="container">
<div class="card my-10">
<div class="card-header" style="background-color: #3c60b9; color: white; height: 2.5em;" class="navbar nav bg-gradient">
<%= link_to Settings.main_site_url, target: '_blank' do %>
<%# Let's take the image from github instead of using the local asset so old emails are less likely to have a broken image url %>
<%= image_tag("https://raw.githubusercontent.com/simonbaird/tiddlyhost/devel/rails/app/assets/images/email-banner.png", style: "display: inline; height: 100%;") %>
<% end %>
</div>
<div class="card-body">
<% if @email_title.present? %><h1 class="h3 mb-2 mt-2"><%= @email_title %></h1><% end %>
<div class="space-y-3 p-2 pt-5">
<%= yield %>
</div>
</div>
</div>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions rails/config/initializers/devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'
config.mailer = 'DeviseBootstrapMailer'

# Configure the parent class responsible to send e-mails.
# config.parent_mailer = 'ActionMailer::Base'
Expand Down
4 changes: 2 additions & 2 deletions rails/test/integration/user_signup_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ class UserSignupTest < CapybaraIntegrationTest
assert_difference('ActionMailer::Base.deliveries.count') { click_button 'Create account' }

# Confirm the "check email" page is shown
assert page.has_content?('check your email for a confirmation link')
assert page.has_content?('please click the confirmation link')

# Sanity check the 'to' address in the confirmation email
confirmation_email = ActionMailer::Base.deliveries.last
assert_equal [email], confirmation_email.to

# Extract the confirmation link from the email and click it
confirmation_link = confirmation_email.body.match(/href="([^"]+)"/)[1]
confirmation_link = confirmation_email.body.encoded.match(%r{href="([^"]+)">Confirm account</a>})[1]
visit confirmation_link

# Login
Expand Down
Loading

0 comments on commit 3d64ba0

Please sign in to comment.