lib/mailgun/messages/batch_message.rb



require 'mailgun/messages/message_builder'

module Mailgun

  # A Mailgun::BatchMessage object is used to create a valid payload
  # for Batch Sending. Batch Sending can be difficult to implement, therefore
  # this code makes it dead simple to send millions of messages in batches of
  # 1,000 recipients per HTTP call.
  #
  # For the curious, the class simply keeps track of recipient data (count,
  # user variables), and fires the API payload on the 1,000th addition of a recipient.
  #
  # The best way to use this class is:
  # 1. Build your message using the Message Builder methods.
  # 2. Query your source and create an iterable list.
  # 3. Iterate through your source data, and add your recipients using the
  #    add_recipient() method.
  # 4. Call finalize() to flush any remaining recipients and obtain/store
  #    the message_ids for tracking purposes.
  #
  # See the Github documentation for full examples.
  class BatchMessage < MessageBuilder

    attr_reader :message_ids, :domain, :recipient_variables

    # Public: Creates a new BatchMessage object.
    def initialize(client, domain)
      @client = client
      @recipient_variables = {}
      @domain = domain
      @message_ids = {}
      @message = Hash.new { |hash, key| hash[key] = [] }

      @counters = {
        recipients: { to: 0, cc: 0, bcc: 0 },
        attributes: { attachment: 0, campaign_id: 0, custom_option: 0, tag: 0 }
      }
    end

    # Adds a specific type of recipient to the batch message object.
    #
    # @param [String] recipient_type The type of recipient. "to".
    # @param [String] address The email address of the recipient to add to the message object.
    # @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
    # @return [void]
    def add_recipient(recipient_type, address, variables = nil)
      # send the message when we have 1000, not before
      send_message if @counters[:recipients][recipient_type] == Mailgun::Chains::MAX_RECIPIENTS

      compiled_address = parse_address(address, variables)
      set_multi_complex(recipient_type, compiled_address)

      store_recipient_variables(recipient_type, address, variables) if recipient_type != :from

      @counters[:recipients][recipient_type] += 1 if @counters[:recipients].key?(recipient_type)
    end

    # Always call this function after adding recipients. If less than 1000 are added,
    # this function will ensure the batch is sent.
    #
    # @return [Hash] A hash of {'Message ID' => '# of Messages Sent'}
    def finalize
      send_message if any_recipients_left?
      @message_ids
    end

    private

    # This method determines if it's necessary to send another batch.
    #
    # @return [Boolean]
    def any_recipients_left?
      return true if @counters[:recipients][:to] > 0
      return true if @counters[:recipients][:cc] > 0
      return true if @counters[:recipients][:bcc] > 0
      false
    end

    # This method initiates a batch send to the API. It formats the recipient
    # variables, posts to the API, gathers the message IDs, then flushes that data
    # to prepare for the next batch. This method implements the Mailgun Client, thus,
    # an exception will be thrown if a communication error occurs.
    #
    # @return [Boolean]
    def send_message
      rkey = 'recipient-variables'
      set_multi_simple rkey, JSON.generate(@recipient_variables)
      @message[rkey] = @message[rkey].first if @message.key?(rkey)

      response = @client.send_message(@domain, @message).to_h!
      message_id = response['id'].gsub(/\>|\</, '')
      @message_ids[message_id] = count_recipients
      reset_message
    end

    # This method stores recipient variables for each recipient added, if
    # variables exist.
    def store_recipient_variables(recipient_type, address, variables)
      variables = { id: @counters[:recipients][recipient_type] } unless variables
      @recipient_variables[address] = variables
    end

    # This method stores recipient variables for each recipient added, if
    # variables exist.
    def count_recipients
      count = 0
      @counters[:recipients].each_value { |cnt| count += cnt }
      count
    end

    # This method resets the message object to prepare for the next batch
    # of recipients.
    def reset_message
      @recipient_variables = {}
      @message.delete('recipient-variables')
      @message.delete(:to)
      @message.delete(:cc)
      @message.delete(:bcc)
      @counters[:recipients][:to] = 0
      @counters[:recipients][:cc] = 0
      @counters[:recipients][:bcc] = 0
    end

  end

end