lib/sendgrid_actionmailer.rb in sendgrid-actionmailer-0.2.1 vs lib/sendgrid_actionmailer.rb in sendgrid-actionmailer-2.0.0
- old
+ new
@@ -1,136 +1,224 @@
require 'sendgrid_actionmailer/version'
require 'sendgrid_actionmailer/railtie' if defined? Rails
-
-require 'fileutils'
-require 'tmpdir'
-
require 'sendgrid-ruby'
module SendGridActionMailer
class DeliveryMethod
- attr_reader :client
+ # TODO: use custom class to customer excpetion payload
+ SendgridDeliveryError = Class.new(StandardError)
- def initialize(params)
- @client = SendGrid::Client.new do |c|
- c.api_user = params[:api_user]
- c.api_key = params[:api_key]
- end
+ include SendGrid
+
+ DEFAULTS = {
+ raise_delivery_errors: false,
+ }
+
+ attr_accessor :settings
+
+ def initialize(**params)
+ self.settings = DEFAULTS.merge(params)
end
def deliver!(mail)
- attachment_temp_dirs = []
- from = mail[:from].addrs.first
-
- email = SendGrid::Mail.new do |m|
- m.to = mail[:to].addresses
- m.cc = mail[:cc].addresses if mail[:cc]
- m.bcc = mail[:bcc].addresses if mail[:bcc]
- m.from = from.address
- m.from_name = from.display_name
- m.reply_to = mail[:reply_to].addresses.first if mail[:reply_to]
- m.date = mail[:date].to_s if mail[:date]
- m.subject = mail.subject
+ sendgrid_mail = Mail.new.tap do |m|
+ m.from = to_email(mail.from)
+ m.reply_to = to_email(mail.reply_to)
+ m.subject = mail.subject
+ # https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html
+ m.add_personalization(to_personalizations(mail))
end
- smtpapi = mail['X-SMTPAPI']
+ add_content(sendgrid_mail, mail)
+ add_send_options(sendgrid_mail, mail)
+ add_mail_settings(sendgrid_mail, mail)
+ add_tracking_settings(sendgrid_mail, mail)
- # If multiple X-SMTPAPI headers are present on the message, then pick the
- # first one. This may happen when X-SMTPAPI is set with defaults at the
- # class-level (using defaults()), as well as inside an individual method
- # (using headers[]=). In this case, we'll defer to the more specific
- # header set in the individual method, which is the first header
- # (somewhat counter-intuitively:
- # https://github.com/rails/rails/issues/15912).
- if(smtpapi.kind_of?(Array))
- smtpapi = smtpapi.first
- end
+ response = perform_send_request(sendgrid_mail)
- if smtpapi && smtpapi.value
- begin
- data = JSON.parse(smtpapi.value)
+ settings[:return_response] ? response : self
+ end
- if data['filters']
- email.smtpapi.set_filters(data['filters'])
- end
+ private
- if data['category']
- email.smtpapi.set_categories(data['category'])
- end
+ def client
+ @client ||= SendGrid::API.new(api_key: settings.fetch(:api_key)).client
+ end
- if data['send_at']
- email.smtpapi.set_send_at(data['send_at'])
- end
+ # type should be either :plain or :html
+ def to_content(type, value)
+ Content.new(type: "text/#{type}", value: value)
+ end
- if data['send_each_at']
- email.smtpapi.set_send_each_at(data['send_each_at'])
- end
+ def to_email(input)
+ to_emails(input).first
+ end
- if data['section']
- email.smtpapi.set_sections(data['section'])
- end
+ def to_emails(input)
+ if input.is_a?(String)
+ [Email.new(email: input)]
+ elsif input.is_a?(::Mail::AddressContainer) && !input.instance_variable_get('@field').nil?
+ input.instance_variable_get('@field').addrs.map do |addr| # Mail::Address
+ Email.new(email: addr.address, name: addr.name)
+ end
+ elsif input.is_a?(::Mail::AddressContainer)
+ input.map do |addr|
+ Email.new(email: addr)
+ end
+ elsif input.is_a?(::Mail::StructuredField)
+ [Email.new(email: input.value)]
+ elsif input.nil?
+ []
+ else
+ puts "unknown type #{input.class.name}"
+ end
+ end
- if data['sub']
- email.smtpapi.set_substitutions(data['sub'])
- end
+ def to_personalizations(mail)
+ Personalization.new.tap do |p|
+ to_emails(mail.to).each { |to| p.add_to(to) }
+ to_emails(mail.cc).each { |cc| p.add_cc(cc) }
+ to_emails(mail.bcc).each { |bcc| p.add_bcc(bcc) }
+ p.add_substitution(Substitution.new(key: "%asm_group_unsubscribe_raw_url%", value: "<%asm_group_unsubscribe_raw_url%>"))
+ p.add_substitution(Substitution.new(key: "%asm_global_unsubscribe_raw_url%", value: "<%asm_global_unsubscribe_raw_url%>"))
+ p.add_substitution(Substitution.new(key: "%asm_preferences_raw_url%", value: "<%asm_preferences_raw_url%>"))
+ end
+ end
- if data['asm_group_id']
- email.smtpapi.set_asm_group(data['asm_group_id'])
- end
+ def to_attachment(part)
+ Attachment.new.tap do |a|
+ a.content = Base64.strict_encode64(part.body.decoded)
+ a.type = part.mime_type
+ a.filename = part.filename
- if data['unique_args']
- email.smtpapi.set_unique_args(data['unique_args'])
- end
+ disposition = get_disposition(part)
+ a.disposition = disposition unless disposition.nil?
- if data['ip_pool']
- email.smtpapi.set_ip_pool(data['ip_pool'])
- end
- rescue JSON::ParserError
- raise ArgumentError, "X-SMTPAPI is not JSON: #{smtpapi.value}"
- end
+ has_content_id = part.header && part.has_content_id?
+ a.content_id = part.header['content_id'].value if has_content_id
end
+ end
- # TODO: This is pretty ugly
+ def get_disposition(message)
+ return if message.header.nil?
+ content_disp = message.header[:content_disposition]
+ return unless content_disp.respond_to?(:disposition_type)
+ content_disp.disposition_type
+ end
+
+ def add_content(sendgrid_mail, mail)
case mail.mime_type
when 'text/plain'
- # Text
- email.text = mail.body.decoded
+ sendgrid_mail.add_content(to_content(:plain, mail.body.decoded))
when 'text/html'
- # HTML
- email.html = mail.body.decoded
+ sendgrid_mail.add_content(to_content(:html, mail.body.decoded))
when 'multipart/alternative', 'multipart/mixed', 'multipart/related'
- email.html = mail.html_part.decoded if mail.html_part
- email.text = mail.text_part.decoded if mail.text_part
+ sendgrid_mail.add_content(to_content(:plain, mail.text_part.decoded)) if mail.text_part
+ sendgrid_mail.add_content(to_content(:html, mail.html_part.decoded)) if mail.html_part
- mail.attachments.each do |a|
- # Write the attachment into a temporary location, since sendgrid-ruby
- # expects to deal with files.
- #
- # We write to a temporary directory (instead of a tempfile) and then
- # use the original filename inside there, since sendgrid-ruby's
- # add_content method pulls the filename from the path (so tempfiles
- # would lead to random filenames).
- temp_dir = Dir.mktmpdir('sendgrid-actionmailer')
- attachment_temp_dirs << temp_dir
- temp_path = File.join(temp_dir, a.filename)
- File.open(temp_path, 'wb') do |file|
- file.write(a.read)
+ mail.attachments.each do |part|
+ sendgrid_mail.add_attachment(to_attachment(part))
+ end
+ end
+ end
+
+ def json_parse(text, symbolize=true)
+ JSON.parse(text.gsub(/:*\"*([\%a-zA-Z0-9_-]*)\"*(( *)=>\ *)/) { "\"#{$1}\":" }, symbolize_names: symbolize)
+ end
+
+ def add_send_options(sendgrid_mail, mail)
+ if mail['template_id']
+ sendgrid_mail.template_id = mail['template_id'].to_s
+ end
+ if mail['sections']
+ json_parse(mail['sections'].value, false).each do |key, value|
+ sendgrid_mail.add_section(Section.new(key: key, value: value))
+ end
+ end
+ if mail['headers']
+ json_parse(mail['headers'].value, false).each do |key, value|
+ sendgrid_mail.add_header(Header.new(key: key, value: value))
+ end
+ end
+ if mail['categories']
+ mail['categories'].value.split(",").each do |value|
+ sendgrid_mail.add_category(Category.new(name: value.strip))
+ end
+ end
+ if mail['custom_args']
+ json_parse(mail['custom_args'].value, false).each do |key, value|
+ sendgrid_mail.add_custom_arg(CustomArg.new(key: key, value: value))
+ end
+ end
+ if mail['send_at'] && mail['batch_id']
+ sendgrid_mail.send_at = mail['send_at'].value.to_i
+ sendgrid_mail.batch_id= mail['batch_id'].to_s
+ end
+ if mail['asm']
+ asm = json_parse(mail['asm'].value)
+ asm = asm.delete_if { |key, value| !key.to_s.match(/(group_id)|(groups_to_display)/) }
+ if asm[:group_id]
+ sendgrid_mail.asm = ASM.new(asm)
+ end
+ end
+ if mail['ip_pool_name']
+ sendgrid_mail.ip_pool_name = mail['ip_pool_name'].to_s
+ end
+ end
+
+ def add_mail_settings(sendgrid_mail, mail)
+ if mail['mail_settings']
+ settings = json_parse(mail['mail_settings'].value)
+ sendgrid_mail.mail_settings = MailSettings.new.tap do |m|
+ if settings[:bcc]
+ m.bcc = BccSettings.new(settings[:bcc])
end
+ if settings[:bypass_list_management]
+ m.bypass_list_management = BypassListManagement.new(settings[:bypass_list_management])
+ end
+ if settings[:footer]
+ m.footer = Footer.new(settings[:footer])
+ end
+ if settings[:sandbox_mode]
+ m.sandbox_mode = SandBoxMode.new(settings[:sandbox_mode])
+ end
+ if settings[:spam_check]
+ m.spam_check = SpamCheck.new(settings[:spam_check])
+ end
+ end
+ end
+ end
- if(mail.mime_type == 'multipart/related' && a.header[:content_id])
- email.add_content(temp_path, a.header[:content_id].field.content_id)
- else
- email.add_attachment(temp_path, a.filename)
+ def add_tracking_settings(sendgrid_mail, mail)
+ if mail['tracking_settings']
+ settings = json_parse(mail['tracking_settings'].value)
+ sendgrid_mail.tracking_settings = TrackingSettings.new.tap do |t|
+ if settings[:click_tracking]
+ t.click_tracking = ClickTracking.new(settings[:click_tracking])
end
+ if settings[:open_tracking]
+ t.open_tracking = OpenTracking.new(settings[:open_tracking])
+ end
+ if settings[:subscription_tracking]
+ t.subscription_tracking = SubscriptionTracking.new(settings[:subscription_tracking])
+ end
+ if settings[:ganalytics]
+ t.ganalytics = Ganalytics.new(settings[:ganalytics])
+ end
end
end
+ end
- client.send(email)
- ensure
- # Close and delete the attachment tempfiles after the e-mail has been
- # sent.
- attachment_temp_dirs.each do |dir|
- FileUtils.remove_entry_secure(dir)
+ def perform_send_request(email)
+ result = client.mail._('send').post(request_body: email.to_json) # ლ(ಠ益ಠლ) that API
+
+ if result.status_code && result.status_code.start_with?('4')
+ message = JSON.parse(result.body).fetch('errors').pop.fetch('message')
+ full_message = "Sendgrid delivery failed with #{result.status_code} #{message}"
+
+ settings[:raise_delivery_errors] ? raise(SendgridDeliveryError, full_message) : warn(full_message)
end
+
+ result
end
end
end