If you're looking for services that handle the delivery of your emails and SMSs in your app, SendGrid and Twilio are some of the most complete options out there.

In this article we are going to focus on a common scenario when using those services: How can we have a real time status of the emails and text messages that we send from our Rails app.

Let's say we have a contacts table with the following columns: name , email , phone_number , email_status and sms_status . When we send an email or SMS with Sendgrid and Twilio, there are a set of possible outcomes that can happen, such as "failed", "delivered", "open" and many more. Here you can see the full list of them:

What we want to achieve is to update our email_status and sms_status columns when one of these events happen. To do that we'll have to use webhooks.

A webhook is an HTTP callback that allows a web application to POST a message to a URL when certain events take place. Often called “Reverse APIs,” Webhooks can be used to receive data in real time, pass it on to another application, or process the data faster than traditional APIs.

A clean way to implement that would be to create two separate endpoints, one for each service:

# config/routes.rb post "sendgrid_webhook" , to : "sendgrid#webhook" post "twilio_webhook" , to : "twilio#webhook"

# app/controllers/sendgrid_controller.rb class SendgridController < ApplicationController skip_before_action :verify_authenticity_token def webhook end end

# app/controllers/twilio_controller.rb class TwilioController < ApplicationController skip_before_action :verify_authenticity_token def webhook end end

We skipped the verify_authenticity_token action so it doesn't raise an InvalidAuthenticityToken exception when the endpoint is accessed from outside of our app. But to keep the security in place it's important to add a custom verification so only requests with a specific token can access the endpoints:

# config/routes.rb post "sendgrid_webhook/:token" , to : "sendgrid#webhook" post "twilio_webhook/:token" , to : "twilio#webhook"

# app/controllers/sendgrid_controller.rb class SendgridController < ApplicationController skip_before_action :verify_authenticity_token , if : :valid_webhook_token? def webhook end private def valid_webhook_token? params [ :token ] == ENV [ "SENDGRID_WEBHOOK_TOKEN" ] end end

# app/controllers/twilio_controller.rb class TwilioController < ApplicationController skip_before_action :verify_authenticity_token , if : :valid_webhook_token? def webhook end private def valid_webhook_token? params [ :token ] == ENV [ "TWILIO_WEBHOOK_TOKEN" ] end end

You can store the tokens as environment variables or whatever method you use to store your credentials:

# .env SENDGRID_WEBHOOK_TOKEN = 1234567890qwertyuiop TWILIO_WEBHOOK_TOKEN = 0987654321poiuytrewq

Once we have the two new endpoints we should add their URL to the Sendgrid and Twilio configuration. For SendGrid that has to be done in their platform:

And for Twilio it has to be done using the status_callback parameter when we send the text message:

def send! client = Twilio :: REST :: Client . new client . api . account . messages . create ( from : "+15005550006" , to : contact . phone_number , body : body , status_callback : "https://www.ombulabs.com/twilio_webhook/ #{ ENV [ 'TWILIO_WEBHOOK_TOKEN' ] } " end

If everything went well, when we send an email or SMS, the SendGrid and Twilio API will hit our endpoints every time there is a change in the status. So now let's add the logic to update the status in our database:

# app/controllers/sendgrid_controller.rb class SendgridController < ApplicationController skip_before_action :verify_authenticity_token , if : :valid_webhook_token? before_action :set_contact def webhook @contact . update_column ( :email_status , sendgrid_params [ :event ] ) render json : {}, status : :ok end private def valid_webhook_token? params [ :token ] == ENV [ "SENDGRID_WEBHOOK_TOKEN" ] end def set_contact @contact = Contact . find_by ( email : sendgrid_params [ :email ] ) end def sendgrid_params params . require ( :_json ) . first . permit ( :email , :event ) end end

# app/controllers/twilio_controller.rb class TwilioController < ApplicationController skip_before_action :verify_authenticity_token , if : :valid_webhook_token? before_action :set_contact def webhook @contact . update_column ( :sms_status , params [ :SmsStatus ] ) render json : {}, status : :ok end private def valid_webhook_token? params [ :token ] == ENV [ "TWILIO_WEBHOOK_TOKEN" ] end def set_contact @contact = Contact . find_by ( email : params [ :To ] ) end end

And that's pretty much it. I recommend that you put a debugger in the webhook method so you can clearly see the parameters you receive so you can tweak it to your needs.

I hope this quick tutorial has been of value to you. Feel free to ask any question in the comments section below.