class ActionMailerKafka::DeliveryMethod

Constants

SUPPORTED_MULTIPART_MIME_TYPES

Attributes

settings[RW]

Public Class Methods

new(**params) click to toggle source

settings params allow you to pass in

  1. Your Kafka publisher

With this option, you pass an instance of Kafka Publisher, which inherit from our ActionMailerKafka::BasesProducer or at least support the method `publish` with the same parameters. After that, your should be as below: config.action_mailer.eh_mailer_settings = {

kafka_mail_topic: 'YourKafkaTopic',
kafka_publisher: PublisherKlass.new

} and the data would go through your publisher instance

  1. Your kafka client info

With this option, the library will generate a kafka instance for you: config.action_mailer.eh_mailer_settings = {

kafka_mail_topic: 'YourKafkaTopic',
kafka_client_info: {
  seed_brokers: ['localhost:9090'],
  logger: logger,
  ssl_ca_cert: '/path/to/cert'
  # For more option on what to pass here, see https://github.com/zendesk/ruby-kafka/blob/master/lib/kafka/client.rb#L20
}

}

Other settings params:

- raise_on_delivery_error
- logger
- fallback
  + fallback_delivery_method
  + fallback_delivery_method_settings
}
# File lib/action_mailer_kafka/delivery_method.rb, line 38
def initialize(**params)
  @settings = params
  # Optional config
  @logger = settings[:logger]
  @raise_on_delivery_error = settings[:raise_on_delivery_error]

  # General configuration
  @service_name = settings[:service_name] || ''
  @mailer_topic_name = settings.fetch(:kafka_mail_topic)
  @kafka_publisher = settings[:kafka_publisher] || ActionMailerKafka::BaseProducer.new(
    logger: @logger, kafka_client_info: settings[:kafka_client_info]
  )

  # Fallback configuration
  @fallback = settings[:fallback]
  if @fallback
    @fallback_delivery_method = Mail::Configuration.instance.lookup_delivery_method(
      @fallback.fetch(:fallback_delivery_method)
    ).new(
      @fallback.fetch(:fallback_delivery_method_settings)
    )
  end
rescue KeyError => e
  raise RequiredParamsError.new(settings, e.message)
end

Public Instance Methods

deliver!(mail) click to toggle source
# File lib/action_mailer_kafka/delivery_method.rb, line 64
def deliver!(mail)
  mail_data = construct_mail_as_kafka_message(mail)
  @kafka_publisher.publish(mail_data, construct_message_key, @mailer_topic_name)
rescue Kafka::Error => e
  error_msg = "Fail to send email into Kafka due to: #{e.message}. Delivered using fallback method"
  @logger&.error(error_msg)
  @fallback_delivery_method.deliver!(mail) if @fallback
  raise KafkaOperationError, error_msg if @raise_on_delivery_error
rescue StandardError => e
  error_msg = "Fail to send email due to: #{e.message}"
  @logger&.error(error_msg)
  raise ParsingOperationError, error_msg if @raise_on_delivery_error
end

Private Instance Methods

construct_attachments(mail) click to toggle source
# File lib/action_mailer_kafka/delivery_method.rb, line 113
def construct_attachments(mail)
  mail.attachments.map { |part| convert_attachment part }
end
construct_custom_mail_header(mail) click to toggle source
# File lib/action_mailer_kafka/delivery_method.rb, line 96
def construct_custom_mail_header(mail)
  result = { custom_headers: {} }
  mail.header_fields.each do |h|
    header_name = h.name
    # header_value = h.unparsed_value
    # Ideally header values should not be parsed and sent directly to the mail service
    # However, Field #unparsed_value is not available on Mail Gem version 2.5 and before
    # here header.value got its value parsed, so a string should be expected
    # even if you create a custom header with a hash
    header_value = h.value
    if h.field.is_a?(::Mail::OptionalField) && header_name.start_with?('X-')
      result[:custom_headers][header_name] = header_value
    end
  end
  result
end
construct_mail_as_kafka_message(mail) click to toggle source
# File lib/action_mailer_kafka/delivery_method.rb, line 80
def construct_mail_as_kafka_message(mail)
  general_data = {
    subject: mail.subject,
    from: mail.from,
    to: mail.to,
    cc: mail.cc,
    bcc: mail.bcc,
    mime_type: mail.mime_type,
    author: @service_name
  }
  general_data.merge! construct_mail_body(mail)
  general_data.merge! construct_custom_mail_header(mail)
  general_data[:attachments] = construct_attachments mail
  general_data.to_msgpack
end
construct_mail_body(mail) click to toggle source
# File lib/action_mailer_kafka/delivery_method.rb, line 125
def construct_mail_body(mail)
  if SUPPORTED_MULTIPART_MIME_TYPES.include?(mail.mime_type)
    {
      text_part: mail.text_part&.decoded,
      html_part: mail.html_part&.decoded
    }
  else
    { body: mail.body&.decoded }
  end
end
construct_message_key() click to toggle source
# File lib/action_mailer_kafka/delivery_method.rb, line 136
def construct_message_key
  # shamelessly copy from https://www.rubydoc.info/github/mikel/mail/Mail%2FUtilities:generate_message_id
  # because some 'mail' version doesn't have this function
  "<#{Mail.random_tag}@#{::Socket.gethostname}.mail>"
end
convert_attachment(part) click to toggle source
# File lib/action_mailer_kafka/delivery_method.rb, line 117
def convert_attachment(part)
  {
    content: Base64.strict_encode64(part.body.decoded),
    type: part.mime_type,
    filename: part.filename
  }
end