0

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("me@example.com", {"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, "john.doe@example.com", {"first" => "John", "last" => "Doe"});
    mb_obj.add_recipient(:to, "jane.doe@example.com", {"first" => "Jane", "last" => "Doe"});
    mb_obj.add_recipient(:to, "bob.doe@example.com", {"first" => "Bob", "last" => "Doe"});
    ...
    mb_obj.add_recipient(:to, "sally.doe@example.com", {"first" => "Sally", "last" => "Doe"});

    # Call finalize to get a list of message ids and totals.
    message_ids = mb_obj.finalize
    # {'id1234@example.com' => 1000, 'id5678@example.com' => 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?

Aarthi
  • 1,451
  • 15
  • 39

1 Answers1

0

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("me@example.com", {"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.

jvillian
  • 19,953
  • 5
  • 31
  • 44
  • What If I need to render a html template for the mail body using `@mail_html = render_to_string layout: mailer_template`, Do I need to pass the values for instance variables used in the mailer template in render_to_string method like `render_to_string layout: mailer_template, locals: { :@user => user}` in this case. – Aarthi Nov 30 '18 at 17:36
  • You have a couple of options for rendering outside of the request-response cycle. You can try [this google search](https://www.google.com/search?q=rails+5+render_to_string+anywhere) or perhaps look at [this question](https://stackoverflow.com/questions/51082180/using-applicationcontroller-new-render-to-string-in-a-rails-api-app-config-ap). – jvillian Nov 30 '18 at 17:44
  • Sorry for unclear question "How can it be tested?". I meant the way of writing testcases for the batchsending. I asked that as mailer method won't return the last statement in the method(Action mailer returns the mail object) and there is no helper available for testing the instance variable too in mailer helpers => These were the cases when I have the method that sends API in mailer. Still same question How can I write test cases using RSpec. I can't find way of writing spec for the above proposed method(Having this under service) in https://relishapp.com/rspec/rspec-rails/docs/ – Aarthi Nov 30 '18 at 17:45
  • Ohh yes, Thanks, I have surfed about rendering the template other than controller and mailer. But I need to pass the instance variables defined as locals. – Aarthi Nov 30 '18 at 17:48