Case studies
At Tealeaf Academy, creating a “Study Together, Progress Together” experience for our students is at core of our way of teaching. One of our core tools is the discussion board where students ask questions, share ideas, collaborate on homework assignments, and teachers quickly jump in to help students get unstuck on problems. One of our recent priorities was to reduce friction in discussion board usage and encourage more discussions with a complementary email notification and a “reply-to email to post on discussion board” workflow.
Once we implemented the below code using the Mailgun Routes API, activity on our discussion board increased three fold, and questions are now typically getting answered within an hour, sometimes even minutes, and students are able to move on the next set of tasks a lot quicker. Here’s how we did it:
Our workflow would go as the following:
The key piece of this workflow is to receive and parse inbound email messages. We looked around for several email service providers, and in the end picked Mailgun because:
class MailgunGateway
def send_batch_message(options={})
RestClient.post(messaging_api_end_point,
from: default_sender,
to: delivery_filter(options[:to]),
subject: options[:subject],
html: options[:body],
:"h:Reply-To" => options[:reply_to],
:"recipient-variables" => options[:recipient_variables]
) if Rails.env.staging? || Rails.env.production?
end
end
private
def default_sender
"Tealeaf Academy "
end
def api_key
@api_key ||= ENV['mailgun_api_key']
end
def messaging_api_end_point
@messaging_api_end_piont ||=
"https://api:#{api_key}@api.mailgun.net/v2/messaging.gotealeaf.com/messages"
end
def delivery_filter(emails)
Rails.env.production? ? emails : "kevin@gotealeaf.com, chris@gotealeaf.com"
end
end
Once a post or comment is created, we send an email notification to all course participants. We are sending emails synchronously for now, but as we have more users, we’ll probably want to offload this to a background job.
Launch School Code 1class Courses::PostsController < AuthenticatedController
expose(:course)
expose(:posts) { course.posts }
expose(:post)
def create
post.user = current_user
post.save
CourseNotifier.new(course).notify_course_participants_on_new_discussion(post)
redirect_to course_home_path(course)
end
...
end
class Courses::Posts::CommentsController < AuthenticatedController
expose(:course)
expose(:posts) { course.posts }
expose(:post)
expose(:comments) { post.comments }
expose(:comment)
def create
comment.user = current_user
comment.save
CourseNotifier.new(course).notify_course_participants_on_new_discussion(comment)
redirect_to course_home_path(course)
end
...
end
The CourseNotifier is the class where we put our application specific logic on notifications. Note that MailgunGateway is injected in as the default gateway – this is from when we used to have multiple email service providers for campaigning, lists and transactional emails. It is less of a need now that we consolidated all email delivery needs to Mailgun!
class CourseNotifier
attr_reader :course, :gateway
def initialize(course, gateway=MailgunGateway.new)
@course = course
@gateway = gateway
end
def notify_course_participants_on_new_discussion(discussion)
gateway.send_batch_message(
to: notification_recipients(discussion).map(&:email).join(", "),
subject: notification_subject(discussion),
body: discussion_notification_text(discussion),
reply_to: reply_to_address(discussion),
recipient_variables: recipient_variables(
notification_recipients(discussion)
)
)
end
...
private
def notification_recipients(discussion)
course.participants.reject {|participant| participant.email == discussion.user.email }
end
def notification_subject(discussion)
discussion.is_a?(Post) ?
"[Tealeaf Academy] #{discussion.user.name} Posted a New Message on the Discussion Board" :
"[Tealeaf Academy] #{discussion.user.name} Replied to a Message on the Discussion Board"
end
def reply_to_address(discussion)
"reply+#{discussion.token}@messaging.gotealeaf.com"
end
def recipient_variables(recipients)
vars = recipients.map do |recipient|
""#{recipient.email}": {"name":"#{recipient.name}"}"
end
"{#{vars.join(', ')}}"
end
def discussion_notification_text(discussion)
<<-EMAIL
<html><body>
Hi %recipient.name%,
<p>#{discussion.user.name} says on the course dicussion board:</p>
"#{discussion.text}"
<br/>
<p>Reply to this email directly or <a href="http://www.gotealeaf.com/courses/#{course.slug}/home">view it on the discussion board</a></p>
</body></html>
EMAIL
end
end
The replytoaddress is where we insert the post token into the “Reply-To” header. The content of the emails are quite simple so we just put them here in the class. If we had a more elaborate email style, we would have used template rendering to handle it. With Mailgun’s sendbatchmessage API, we can call the API just once to send to multiple recipients, and the recipient_variables method is where we customize email messages for each receiver to include their names to add a personal touch.
Heading over to Mailgun, under “Routes” in the Control Panel, we created a route as the following:
When a user replies to an email, they reply it to an email address such as “reply+fj42gq4v@messaging.gotealeaf.com”, and this route will forward the email to a web hook that we expose to handle incoming messages.
class Api::IncomingMessagesController < ApplicationController
skip_before_filter :verify_authenticity_token
def create
user = User.where(email: params['sender']).first
post = Post.where(token: params['post_token']).first
text = params["stripped-text"]
if post && user && text.present?
comment = post.comments.create(user: user, text: text)
CourseNotifier.new(post.course).notify_course_participants_on_new_discussion(comment)
end
head(200)
end
end
Here, we use the sender’s email to find the author, and use the post token to find the post that this reply should be collated under. Mailgun gives us the very useful stripped text which strips away the original message part to only contain the actual reply! 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 faithfully keep trying to call our webhook.
The result from implementing this workflow is impressive – activity on our discussion board increased three fold, and questions are now typically getting answered within an hour, sometimes even minutes, students are able to move on the next set of tasks a lot quicker and we are very happy how this turned out.
“We’re able to ensure a better service for our marketers by using Mailgun. They count on us, and we count on Mailgun: and this relationship helps us maintain credibility with our customers.”
Send me the Mailjet Newsletter. I expressly agree to receive the newsletter and know that I can easily unsubscribe at any time.