Search code examples
ruby-on-railsrspecruby-on-rails-5rspec-railsmailgun

Sending emails in Rails using HTTP protocol(MailGun API)


I am trying to sending emails using MailGun's batch sending API using MailGun ruby sdk(https://github.com/mailgun/mailgun-ruby/blob/master/docs/MessageBuilder.md). As of now I have this method inside a class which inherits from ActionMailer.

class BatchMailer < ApplicationMailer
  def send_batch_email(mail, recipients)

    # First, instantiate the Mailgun Client with your API key
    mg_client = Mailgun::Client.new("your-api-key")

    # Create a Batch Message object, pass in the client and your domain.
    mb_obj = Mailgun::BatchMessage.new(mg_client, "example.com")

    # Define the from address.
    mb_obj.from("[email protected]", {"first" => "Ruby", "last" => "SDK"});

    # Define the subject.
    mb_obj.subject("A message from the Ruby SDK using Message Builder!");

    # Define the body of the message.
    mb_obj.body_text("This is the text body of the message!");


    # Loop through all of your recipients
    mb_obj.add_recipient(:to, "[email protected]", {"first" => "John", "last" => "Doe"});
    mb_obj.add_recipient(:to, "[email protected]", {"first" => "Jane", "last" => "Doe"});
    mb_obj.add_recipient(:to, "[email protected]", {"first" => "Bob", "last" => "Doe"});
    ...
    mb_obj.add_recipient(:to, "[email protected]", {"first" => "Sally", "last" => "Doe"});

    # Call finalize to get a list of message ids and totals.
    message_ids = mb_obj.finalize
    # {'[email protected]' => 1000, '[email protected]' => 15}
  end
end

Is is a correct way to keep the method that doesn't use actionmailer to send emails inside mailer?

ActionMailer method returns mail object but when trying to write spec for the method that uses API to send emails I can't able to get response as there won't be a mail object(ActionMailer message object). Where to keep this method and how it can be tested?


Solution

  • Is this a correct way to keep the method that doesn't use actionmailer to send emails inside mailer?

    There is no reason to use a Mailer in this case. Simply use a service object (a plain-old ruby object or PORO). It might look something like:

    class BatchMailerService
    
      attr_accessor *%w(
        mail 
        recipients
        recipient
      ).freeze
    
      delegate *%w(
        from
        subject
        body_text
        add_recipient
        finalize
      ), to: :mb_obj
    
      delegate *%w(
        address 
        first_name 
        last_name
      ), to: :recipient, prefix: true
    
        class << self 
    
          def call(mail, recipients)
            new(mail, recipients).call
          end
    
        end # Class Methods
    
      #==============================================================================================
      # Instance Methods
      #==============================================================================================
    
        def initialize(mail, recipients)
          @mail, @recipients = mail, recipients
        end
    
        def call
          setup_mail
          add_recipients
          message_ids = finalize
        end
    
      private 
    
        def mg_client
          @mg_client ||= Mailgun::Client.new(ENV["your-api-key"])
        end
    
        def mb_obj
          @mb_obj ||= Mailgun::BatchMessage.new(mg_client, "example.com")
        end
    
        def setup_mail
          from("[email protected]", {"first" => "Ruby", "last" => "SDK"})
          subject("A message from the Ruby SDK using Message Builder!")
          body_text("This is the text body of the message!")
        end
    
        def add_recipients
          recipients.each do |recipient|
            @recipient = recipient
            add_recipient(
              :to, 
              recipient_address, 
              {
                first:  recipient_first_name,
                last:   recipient_last_name
              }
            )
          end
        end
    
    end
    

    Which you would use something like:

    BatchMailerService.call(mail, recipients)
    

    (assuming, naturally, that you have variables called mail and recipients).

    Where to keep this method?

    You might place that file in app/services/batch_mailer_service.rb.

    How can it be tested?

    What do you mean? How you test the service depends on what your criteria for success are. You could test that mb_obj receives the finalize call (maybe using something like expect().to receive). You could test message_ids contains the correct information (maybe using something like expect().to include). It sort of depends.