module Sisimai::Reason

Sisimai::Reason detects the bounce reason from the content of Sisimai::Data object as an argument of get() method. This class is called only Sisimai::Data class.

Constants

ClassOrder
GetRetried
ModulePath

Public Class Methods

anotherone(argvs) click to toggle source

Detect the other bounce reason, fall back method for get() @param [Sisimai::Data] argvs Parsed email object @return [String, Nil] Bounce reason or nli if the argument

is missing or invalid object

@see get

# File lib/sisimai/reason.rb, line 119
def anotherone(argvs)
  return nil unless argvs.is_a? Sisimai::Data
  return argvs.reason unless argvs.reason.empty?

  require 'sisimai/smtp/status'
  statuscode = argvs.deliverystatus || ''
  reasontext = Sisimai::SMTP::Status.name(statuscode) || ''

  catch :TRY_TO_MATCH do
    while true
      diagnostic   = argvs.diagnosticcode.downcase || ''
      trytomatch   = reasontext.empty? ? true : false
      trytomatch ||= true if GetRetried[reasontext]
      trytomatch ||= true if argvs.diagnostictype != 'SMTP'
      throw :TRY_TO_MATCH unless trytomatch

      # Could not decide the reason by the value of Status:
      ClassOrder[1].each do |e|
        # Trying to match with other patterns in Sisimai::Reason::* classes
        p = 'Sisimai::Reason::' << e
        r = nil
        begin
          require ModulePath[p]
          r = Module.const_get(p)
        rescue
          warn ' ***warning: Failed to load ' << p
          next
        end

        next unless r.match(diagnostic)
        reasontext = e.downcase
        break
      end
      throw :TRY_TO_MATCH unless reasontext.empty?

      # Check the value of Status:
      v = statuscode[0, 3]
      if v == '5.6' || v == '4.6'
        #  X.6.0   Other or undefined media error
        reasontext = 'contenterror'

      elsif v == '5.7' || v == '4.7'
        #  X.7.0   Other or undefined security status
        reasontext = 'securityerror'

      elsif %w[X-UNIX X-POSTFIX].include?(argvs.diagnostictype)
        # Diagnostic-Code: X-UNIX; ...
        reasontext = 'mailererror'
      else
        # 50X Syntax Error?
        require 'sisimai/reason/syntaxerror'
        reasontext = 'syntaxerror' if Sisimai::Reason::SyntaxError.true(argvs)
      end
      throw :TRY_TO_MATCH unless reasontext.empty?

      # Check the value of Action: field, first
      if argvs.action.start_with?('delayed', 'expired')
        # Action: delayed, expired
        reasontext = 'expired'
      else
        # Rejected at connection or after EHLO|HELO
        commandtxt = argvs.smtpcommand || ''
        reasontext = 'blocked' if %w[HELO EHLO].index(commandtxt)
      end
      throw :TRY_TO_MATCH
    end
  end
  return reasontext
end
get(argvs) click to toggle source

Detect the bounce reason @param [Sisimai::Data] argvs Parsed email object @return [String, Nil] Bounce reason or Nil if the argument

is missing or invalid object

@see anotherone

# File lib/sisimai/reason.rb, line 62
def get(argvs)
  return nil unless argvs
  return nil unless argvs.is_a? Sisimai::Data

  unless GetRetried[argvs.reason]
    # Return reason text already decided except reason match with the
    # regular expression of retry() method.
    return argvs.reason unless argvs.reason.empty?
  end
  return 'delivered' if argvs.deliverystatus.start_with?('2.')

  reasontext = ''
  if argvs.diagnostictype == 'SMTP' || argvs.diagnostictype == ''
    # Diagnostic-Code: SMTP; ... or empty value
    ClassOrder[0].each do |e|
      # Check the value of Diagnostic-Code: and the value of Status:, it is a
      # deliverystats, with true() method in each Sisimai::Reason::* class.
      p = 'Sisimai::Reason::' << e
      r = nil
      begin
        require ModulePath[p]
        r = Module.const_get(p)
      rescue
        warn ' ***warning: Failed to load ' << p
        next
      end
      next unless r.true(argvs)
      reasontext = r.text
      break
    end
  end

  if reasontext.empty? || reasontext == 'undefined'
    # Bounce reason is not detected yet.
    reasontext = self.anotherone(argvs)

    if reasontext == 'undefined' || reasontext.empty?
      # Action: delayed => "expired"
      reasontext   = nil
      reasontext ||= 'expired' if argvs.action == 'delayed'
      return reasontext if reasontext

      # Try to match with message patterns in Sisimai::Reason::Vacation
      require 'sisimai/reason/vacation'
      reasontext   = 'vacation' if Sisimai::Reason::Vacation.match(argvs.diagnosticcode.downcase)
      reasontext ||= 'onhold'   unless argvs.diagnosticcode.empty?
      reasontext ||= 'undefined'
    end
  end
  return reasontext
end
index() click to toggle source

All the error reason list Sisimai support @return [Array] Reason list

# File lib/sisimai/reason.rb, line 10
def index
  return %w[
    Blocked ContentError ExceedLimit Expired Filtered HasMoved HostUnknown
    MailboxFull MailerError MesgTooBig NetworkError NotAccept OnHold
    Rejected NoRelaying SpamDetected VirusDetected PolicyViolation SecurityError
    Suspend SystemError SystemFull TooManyConn UserUnknown SyntaxError
  ]
end
match(argv1) click to toggle source

Detect the bounce reason from given text @param [String] argv1 Error message @return [String] Bounce reason

# File lib/sisimai/reason.rb, line 192
def match(argv1)
  return nil unless argv1

  reasontext = ''
  diagnostic = argv1.downcase

  # Diagnostic-Code: SMTP; ... or empty value
  ClassOrder[2].each do |e|
    # Check the value of Diagnostic-Code: and the value of Status:, it is a
    # deliverystats, with true() method in each Sisimai::Reason::* class.
    p = 'Sisimai::Reason::' << e
    r = nil
    begin
      require ModulePath[p]
      r = Module.const_get(p)
    rescue
      warn ' ***warning: Failed to load ' << p
      next
    end

    next unless r.match(diagnostic)
    reasontext = r.text
    break
  end
  return reasontext unless reasontext.empty?

  typestring = ''
  if cv = argv1.match(/\A(SMTP|X-.+);/i)
    # Check the value of typestring
    typestring = cv[1].upcase
  end

  if typestring == 'X-UNIX'
    # X-Unix; ...
    reasontext = 'mailererror'
  else
    # Detect the bounce reason from "Status:" code
    require 'sisimai/smtp/status'
    statuscode = Sisimai::SMTP::Status.find(argv1) || ''
    reasontext = Sisimai::SMTP::Status.name(statuscode) || 'undefined'
  end
  return reasontext
end
path() click to toggle source

@abstract Returns Sisimai::Reason::* module path table @return [Hash] Module path table @since v4.25.6

# File lib/sisimai/reason.rb, line 22
def path
  index = Sisimai::Reason.index
  table = {}
  index.each { |e| table['Sisimai::Reason::' << e] = 'sisimai/reason/' << e.downcase }
  return table
end
retry() click to toggle source

Reason list better to retry detecting an error reason @return [Array] Reason list

# File lib/sisimai/reason.rb, line 31
def retry
  return {
    'undefined' => 1, 'onhold' => 1, 'systemerror' => 1, 'securityerror' => 1,
    'networkerror' => 1, 'hostunknown' => 1, 'userunknown' => 1
  }.freeze
end