Send Emails using SendGrid SDK for Ruby on Rails using Dynamic SendGrid Email Templates

How to send an email with attachments using SendGrid’s SDK for Ruby on Rails and dynamic templates. This article supports transactional and marketing emails.

Send Emails using SendGrid SDK for Ruby on Rails using Dynamic SendGrid Email Templates

In the previous article, ‘Mail Sending API perks over SMTP in Ruby on Rails using MailGun or SendGrid,’ I explained many advantages of using API over SMTP.

In this article, I will explain more about sending emails using SendGrid SDK in Ruby on Rails, involving SendGrid’s dynamic templates, and even including attachments to the email.

Adding SendGrid SDK to Rails

In Gemfile, add official SendGrid gem:

gem 'sendgrid-ruby', '~> 6.6', '>= 6.6.2'

And run bundle install.

Dynamic Templates for SendGrid

SendGrid’s more robust feature, in my opinion, is the ability to add dynamic templates. You can create a template using their visual builder or using HTML. Both cases provide a nice responsive preview of the email so that you can create beautiful templates easily.

These templates also provide options for substitutions. For example, you are creating a transactional email, and the receiver’s name is dependent on the person to whom you are sending that email. So mention a variable in the template like {{name}}, and when you send an email through SDK, send a JSON object for substitution value like below:

  "name": "Sulman Baig"

When you are done creating the template, you will get template_id. Save that ID for future use.

Get SendGrid’s API Key:

You can get SendGrid’s API Key by following the steps mentioned in their official documentation:

Email Job:

Now that you have template_id and API Key, let’s start creating the email job that will send an email to a user in the database using a dynamic template and attachments.

We are assuming for the below code snippet:

  • EXPORT_TEMPLATE ENV contains the template ID you created
  • SENDGRID_API_KEY ENV contains SendGrid’s API Key.
  • @tempfile is the file saved in local to send as an attachment
  • user_id is the user saved in the database with attributes like name and email.

Now create a file app/jobs/email_job.rb and add the following code.

You can include the sidekiq gem and call this job asynchronously.
# frozen_string_literal: true

require 'sendgrid-ruby'

#### Example Call
#         { name: },
#         ENV.fetch('EXPORT_TEMPLATE', nil),
#         [
#           {
#             file: @tempfile.path,
#             type: 'application/csv',
#             name: @tempfile.path.split('/').last,
#             content_id: 'export_file'
#           }
#         ]
#       )

# This is the email job that will be sent to the user
class EmailJob
  include SendGrid

  # From Email and Name
  NOREPLY_FROM_NAME = 'All Wallet'

  # include sidekiq to call perform as perform_async
  def perform(user_id, subsitutions, template_id, attachments = nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
    # initialize sendgrid api
    sg = ENV.fetch('SENDGRID_API_KEY', nil))
    # we will get to_email from user object saved in db
    user = User.kept.find_by(id: user_id)
    return unless user

    # initialize mail object of sendgrid
    mail =
    # fill 'from' data from the constants mentioned above
    # personalization is object for email to data and templates
    personalization =
    # add user data to which email to be sent
    personalization.add_to(, name:
    # add substitutions to template created in sendgrid to replace the variable in template like `{{name}}`
    # {
    #   "name": "Sulman Baig"
    # }
    mail.template_id = template_id

    # If attachments are sent as arguments
    if attachments.present? && attachments.is_a?(Array) && attachments.size.positive?
      attachments.each do |attachment_input|
        attachment =
        # attachment has to be sent as base64 encoded string
        attachment.content = Base64.strict_encode64([:file])) # file: path of file saved in local or remote
        attachment.type = attachment_input[:type] # type of file e.g. application/csv
        attachment.filename = attachment_input[:name] # filename
        attachment.disposition = 'attachment'
        attachment.content_id = attachment_input[:content_id] # e.g. export_file

      # Send Email
      sg.client.mail._('send').post(request_body: mail.to_json)
    rescue StandardError => e
      # TODO: capture exception

GitHub Gist

Example call for the above job be like:,
        { name: },
        ENV.fetch('EXPORT_TEMPLATE', nil),
            file: @tempfile.path,
            type: 'application/csv',
            name: @tempfile.path.split('/').last,
            content_id: 'export_file'

Happy Coding!