Skip to content

Instantly share code, notes, and snippets.

@AlexanderMint
Last active March 12, 2018 10:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AlexanderMint/ecd0139c3f3db8cf52bd3676188d5c8e to your computer and use it in GitHub Desktop.
Save AlexanderMint/ecd0139c3f3db8cf52bd3676188d5c8e to your computer and use it in GitHub Desktop.
Example SendGrid mailer
@AlexanderMint
Copy link
Author

sendgrid_mailer.rb

# frozen_string_literal: true

module Mailer
  class SendGridMailer
    require 'net/http'

    DSL_METHODS = %i[to from template_id].freeze
    NONE = Object.new.freeze

    DSL_METHODS.each do |meth|
      define_method(meth) do |value = NONE|
        ivar = "@#{meth}"

        value == NONE ? instance_variable_get(ivar) : instance_variable_set(ivar, value)
      end
    end

    ENDPOINT_URI = URI('https://api.sendgrid.com/v3/mail/send')
    HEADERS = { 'authorization' => 'Bearer ' + ENV['SENDGRID_API_KEY'],
                'Content-Type' => 'application/json' }.freeze

    attr_reader :user

    def initialize(user)
      @user = user

      from email: ENV['EMAIL_FROM']
    end

    def data
      {
        personalizations: [{
          to: fetch_to,
          substitutions: @substitutions
        }],
        from: from,
        template_id: fetch_template_id,
        mail_settings: mail_settings
      }
    end

    def mail_settings
      { sandbox_mode: { enable: Hanami.env?('test') } }
    end

    def send_email
      response = Net::HTTP.post(ENDPOINT_URI, data.to_json, HEADERS)

      validate_response!(response)

      response
    end

    def options(&block)
      opts = Options.new
      instance_exec(opts, &block)
      @substitutions = opts.options
    end

    class Options
      attr_reader :options

      def initialize
        @options = substitution(:root_url, ENV['CLIENT_URL'])
      end

      def method_missing(meth, *args, &block)
        return super unless meth.to_s.end_with?('=') && args.size == 1

        options[substitution(meth[0..-2])] = args.first.to_s
      end

      def respond_to_missing?(meth, include_private = false)
        return super if include_private || !meth.to_s.end_with?('=')

        true
      end

      private

      def substitution(name, value = nil)
        value ? { "<%#{name}%>" => value } : "<%#{name}%>"
      end
    end

    class << self
      def mail(method_name, &block)
        define_method(method_name) do
          instance_eval(&block)

          send_email
        end
      end
    end

    class SendGridAPIError < StandardError
    end

    private

    def validate_response!(response)
      return if response.code == '202'

      exception = SendGridAPIError.new(response.body)
      exception.set_backtrace(caller)

      Bugsnag.notify(exception)
    end

    def fetch_to
      if Hanami.env?(:production, :test)
        to.map { |email| { email: email } }
      else
        [{ email: ENV['EMAIL_TO'] }]
      end
    end

    def fetch_template_id
      template_id.fetch(user.language.intern) do
        template_id.values.first
      end
    end
  end
end

user_mailer.rb

# frozen_string_literal: true

require_relative 'sendgrid_mailer'

module Mailer
  class UserMailer < SendGridMailer
    mail(:confirmation_token) do
      to [user.email]

      options do |option|
        option.first_name = user.first_name
        option.last_name = user.last_name
        option.confirmation_token = user.confirmation_token
      end

      template_id en: ENV['USER_CONFIRMATION_TOKEN_TEMP_EN'],
                  ru: ENV['USER_CONFIRMATION_TOKEN_TEMP_RU']
    end

    mail(:password_recovery) do
      to [user.email]

      options do |option|
        option.first_name = user.first_name
        option.last_name = user.last_name
        option.confirmation_token = user.reset_password_token
      end

      template_id en: ENV['USER_PASSWORD_RECOVERY_TEMP_EN'],
                  ru: ENV['USER_PASSWORD_RECOVERY_TEMP_RU']
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment