# frozen_string_literal: true class HTTPSender < BaseSender def initialize(endpoint, options = {}) super() @endpoint = endpoint @options = options @log_id = SecureRandom.alphanumeric(8).upcase end def send_message(message) start_time = Time.now result = SendResult.new result.log_id = @log_id request_options = {} request_options[:sign] = true request_options[:timeout] = @endpoint.timeout || 5 case @endpoint.encoding when "BodyAsJSON" request_options[:json] = parameters(message, flat: false).to_json when "FormData" request_options[:params] = parameters(message, flat: true) end log "Sending request to #{@endpoint.url}" response = Postal::HTTP.post(@endpoint.url, request_options) result.secure = !!response[:secure] # rubocop:disable Style/DoubleNegation result.details = "Received a #{response[:code]} from #{@endpoint.url}" log " -> Received: #{response[:code]}" if response[:body] log " -> Body: #{response[:body][0, 255]}" result.output = response[:body].to_s[0, 500].strip end if response[:code] >= 200 && response[:code] < 300 # This is considered a success result.type = "Sent" elsif response[:code] >= 500 && response[:code] < 600 # This is temporary. They might fix their server so it should soft fail. result.type = "SoftFail" result.retry = true elsif response[:code].negative? # Connection/SSL etc... errors result.type = "SoftFail" result.retry = true result.connect_error = true elsif response[:code] == 429 # Rate limit exceeded, treat as a hard fail and don't send bounces result.type = "HardFail" result.suppress_bounce = true else # This is permanent. Any other error isn't cool with us. result.type = "HardFail" end result.time = (Time.now - start_time).to_f.round(2) result end private def log(text) Postal.logger.info text, id: @log_id, component: "http-sender" end def parameters(message, options = {}) case @endpoint.format when "Hash" hash = { id: message.id, rcpt_to: message.rcpt_to, mail_from: message.mail_from, token: message.token, subject: message.subject, message_id: message.message_id, timestamp: message.timestamp.to_f, size: message.size, spam_status: message.spam_status, bounce: message.bounce, received_with_ssl: message.received_with_ssl, to: message.headers["to"]&.last, cc: message.headers["cc"]&.last, from: message.headers["from"]&.last, date: message.headers["date"]&.last, in_reply_to: message.headers["in-reply-to"]&.last, references: message.headers["references"]&.last, html_body: message.html_body, attachment_quantity: message.attachments.size, auto_submitted: message.headers["auto-submitted"]&.last, reply_to: message.headers["reply-to"] } if @endpoint.strip_replies hash[:plain_body], hash[:replies_from_plain_body] = ReplySeparator.separate(message.plain_body) else hash[:plain_body] = message.plain_body end if @endpoint.include_attachments? if options[:flat] message.attachments.each_with_index do |a, i| hash["attachments[#{i}][filename]"] = a.filename hash["attachments[#{i}][content_type]"] = a.content_type hash["attachments[#{i}][size]"] = a.body.to_s.bytesize.to_s hash["attachments[#{i}][data]"] = Base64.encode64(a.body.to_s) end else hash[:attachments] = message.attachments.map do |a| { filename: a.filename, content_type: a.mime_type, size: a.body.to_s.bytesize, data: Base64.encode64(a.body.to_s) } end end end hash when "RawMessage" { id: message.id, rcpt_to: message.rcpt_to, mail_from: message.mail_from, message: Base64.encode64(message.raw_message), base64: true, size: message.size.to_i } else {} end end end