Back to main menu

IT & Engineering

Building a data-driven approach to keeping user’s emails up-to-date

This blog was written by Roy Young. Roy a Ruby developer honing his skills at Tealeaf Academy. Ah, the “users” table.



This blog was written by Roy Young. Roy a Ruby developer honing his skills at Tealeaf Academy.

Ah, the “users” table.

One could argue that it is one of the most important assets of a start-up. The cynics out there can’t believe that Instagram (a photo sharing app!) was bought for $715 million by Facebook or that Mailbox, a mobile email client for Gmail (and still in beta in many respects), was rumored to be bought for up to $100 million by Dropbox.

This cynical view often results from a myopic focus on just the product or the technology, which makes it easy to dismiss these valuations as absurd. However, what also needs to be considered is the value of all of those users and the growth of that user base. These astronomical valuations are always associated with a product that has a large and fast growing user base. It stands to reason, then, that keeping your user table up to date with correct contact information is a good investment for your business. Of course, the hard part is creating that growth…so you’ll have to pay for that information.

This advice on the “easy part” is free, though. In this post I’ll talk about a good methodology for keeping your users’ email up to date in an age when many people have multiple email addresses and throw-away email addresses for many of their services.

Application logic for prompting user to update their email address

When you send email to an email address that no longer exists, the receiving party (e.g. Gmail or Yahoo) will bounce the email back to you. If you listen to these bounces, your app can react appropriately, creating a great user experience while keeping your customer database up-to-date. This blog outlines a sample application built on Heroku that uses Mailgun to listen to these bounced emails so that users with bad email addresses can continue to receive monthly invoice emails. The same logic could be applied to any of your emails, however, making sure that your users stay engaged and up-to-date with your company via email.

I’ve created a demo site for this article and made the source code available on Github. This demo walks a novice through the entire process of setting up this functionality. Click here if you want to skip directly to integrating bounce web hooks into your app.

Sending monthly invoice emails using Heroku Scheduler

To send monthly invoices to your users, you’ll need to write code to:

  • Deploy customized invoice emails to your hundreds, or thousands or millions of customers.

  • Actually schedule the deployment of these emails.

Let’s look at each of these in turn.

Deploying customized invoice emails

For the purposes of this tutorial, I’ll assume you already have a Mailgun account. From there, you’ll want to send your customer list to Mailgun, along with any variables that you want dynamically inserted into the emails as a JSON file and the HTML or text body of the email. Here is the code.

class MailgunGateway include Rails.application.routes.url_helpers def send_batch_message(users), from: "Mailgun Demo <billing@#{ENV["mailgun_domain_name"]}>", to:", "), subject: "Monthly Billing Info", html: billing_info_text, :"h:Reply-To" => "billing@#{ENV["mailgun_domain_name"]}", :"recipient-variables" => recipient_variables(billing_recipients) ) end private def api_key @api_key ||= ENV["mailgun_api_key"] end def messaging_api_end_point @messaging_api_end_piont ||= "https://api:#{api_key}{ENV["mailgun_domain_name"]}/messages" end def billing_recipients @users ||= User.where(locked: false) end def recipient_variables(recipients) vars = do |recipient| ""#{}": {"name":"#{recipient.fullname}"}" end "{#{vars.join(', ')}}" end def billing_info_text <<-EMAIL <html><body> Hi, <p> Your bill for the current month is now available, please click on <br/> #{billing_url} <br/> to see details. </p> <p>Reply to this email directly</p> </body></html> EMAIL end end

Lets look at what we did.

In the sendbatchmessages method, we use Recipient Variables so that we can define variables to customize each email. You’ll notice that we are inserting the user’s first name in the email. You could compose an invoice email that includes details like account number, invoice account, or anything else you like, but I’m keeping things simple.

Sending out automatic monthly billing emails with Heroku Scheduler

Once we have our method for creating and deploying emails via Mailgun, we need to write some code that automatically triggers this method. I’ve chosen to deploy this app on Heroku and am using Heroku Scheduler because it does the job, is a free add-on, and is easy to integrate.

Heroku Scheduler will run a specified Rake task at a specified time. So here we need to build a Rake task to send billing emails using Mailgun in lib/tasks/scheduler.rake.

First, when we send billing invoices to users, we need to find the email addresses of the customer we need to invoice.

In this sample app’s very simple database schema, we have a :locked attribute that is set to True when an email has bounced previously (more on that below). So our query simply pulls all our users where this field is False.

To actually send the email, I’m using Mailgun Batch Sending. Because the maximum number of recipients allowed per API call is 1,000, we use findinbatches to send billing emails to Mailgun in batches of 1000 recipients.

desc "This task is called by the Heroku scheduler add-on" task :send_billing_info => :environment do if == 1 User.where(locked: false).find_in_batches(batch_size: 1000) do |group| end end end

Beacuse Heroku Scheduler “FREQUENCY” setting has only three selectors— “daily, hourly, every 10 mins”— we need to check whether the current day is the first day of the month using if == 1. If it is, the email is deployed. If not, no email.

Receiving notification of bounced emails

When an email is bounced, Mailgun will send a POST via the bounce webhook to our app. You need to set the callback url under “Bounces” in the Mailgun Control Panel. In this demo, my callback url is “”

When the app receives the bounce notification, the app will find the email address in the customer database, and set the user status to locked, so the app will not send billing email to the user until the user update his or her email address.

class Api::BouncedMailsController < ApplicationController skip_before_filter :verify_authenticity_token def create if verify(ENV["mailgun_api_key"], params[:token], params[:timestamp], params[:signature]) user = User.find_by_email(params[:recipient]) if user && params[:event] == "bounced" user.lock! end head(200) end end private def verify(api_key, token, timestamp, signature) signature == OpenSSL::HMAC.hexdigest('sha256'), api_key, '%s%s' % [timestamp, token]) end end

The user.lock! is to set user status to locked, so our Rake Task will never send emails to this user. This method is set in User model.

class User < ActiveRecord::Base … def lock! self.locked = true save(validate: false) end end

The verify method is to verify the webhook is originating from Mailgun, otherwise any others could send a fake POST to lock the users in your app.

For more details on configuring web hooks you can read the Mailgun documentation – Events/Webhooks.

In the end, we return a 200 header to tell Mailgun that this interaction is successful, otherwise Mailgun will think our server is down and will keep trying to call our webhook.

Displaying the flash screen for a user to update their email

Once the app knows which users have a bad email address, we need to display a flash screen to the user upon their next login with a prompt to verify their contact information on file.

class SessionsController < ApplicationController def new redirect_to home_path if current_user end def create user = User.find_by_email(params[:email]) if user && user.authenticate(params[:password]) session[:user_id] = if user.locked flash[:error] = "Your Email Address is invalid, please update it." redirect_to edit_user_path(current_user) else redirect_to home_path end else flash[:error] = "Incorrect email or password. Please try again." redirect_to sign_in_path end end def destroy session[:user_id] = nil redirect_to root_path end end

Once they’ve updated their email address, we set the locked attribute back to false so that they will continue to receive emails.

That’s it. Now, when your users email’s bounce, you can prompt them to update their information, providing a great user experience with the added benefit of keeping your customer data fresh.

Related readings

What is DKIM: Learn how it works and why it’s necessary

Are you who you say you are, or are you a spoofer in disguise? Answering this question is what DKIM is all about. As email usage and capabilities continue to grow, it’s important to...

Read more

Email hard bounces: The brick walls of failure

We’ve talked a lot about email bounces and email bounce rates as a whole in the past before, but we’ve really only ever dipped our toes into the different types of bounces...

Read more

How does email work? Decode the mysteries with our straightforward guide

What is email? Well, we know it’s a cheap, effective form of communication that is so widely used there’s an entire global infrastructure to support it. We rely on email for...

Read more

Popular posts

Email inbox.

Build Laravel 10 email authentication with Mailgun and Digital Ocean

When it was first released, Laravel version 5.7 added a new capability to verify user’s emails. If you’ve ever run php artisan make:auth within a Laravel app you’ll know the...

Read more

Mailgun statistics.

Sending email using the Mailgun PHP API

It’s been a while since the Mailgun PHP SDK came around, and we’ve seen lots of changes: new functionalities, new integrations built on top, new API endpoints…yet the core of PHP...

Read more

Statistics on deliverability.

Here’s everything you need to know about DNS blocklists

The word “blocklist” can almost seem like something out of a movie – a little dramatic, silly, and a little unreal. Unfortunately, in the real world, blocklists are definitely something you...

Read more

See what you can accomplish with the world's best email delivery platform. It's easy to get started.Let's get sending
CTA icon