Class: MailBounce::Rejection

Inherits:
Object
  • Object
show all
Defined in:
lib/mailbounce/rejection.rb

Overview

Classifies an SMTP rejection: recipient fault vs sender/exchange.

Constant Summary collapse

REPLY_CODE =
/\A\s*(?<code>[245]\d\d)\b/
INVALID_CONDITIONS =

RFC 3463 §3.2, class dropped. 1.2 is the system; permanently reported it is as dead as a mailbox (transient 4.1.2 is merely unreachable).

%w[ 1.1 1.2 1.3 1.6 2.1 ].freeze
FULL_CONDITIONS =
%w[ 2.2 ].freeze
OVERSIZED_CONDITIONS =
%w[ 2.3 3.4 ].freeze
SENDER_CONDITIONS =

RFC 3463 §3.2: sender's address, not the recipient's.

%w[ 1.7 1.8 ].freeze
BLOCKED_SUBJECTS =

Network, system, protocol, policy — the exchange, not the mailbox.

%w[ 3 4 5 7 ].freeze
INVALID_TEXT =

Common MTA wording when no enhanced status is present. No bare "does not exist": that predicate takes any noun, so "sender address does not exist" would read as a dead recipient. No "mailbox unavailable": RFC 5321 §4.2.3 uses that phrase for both 450 (busy / temp policy) and 550 (not found / no access / policy).

/user unknown|unknown user|no such (?:user|recipient)|recipient not found|invalid recipient/i
FULL_TEXT =
/mailbox full|over quota/i
BLOCKED_TEXT =
/blocked|blacklist|spam|denied/i
QUAD =
/(?:\d{1,3}\.){3}\d{1,3}/
ADDRESS =

Mapped form first: otherwise \h*(?::\h*){2,} stops at ::ffff:203.

/\h*(?::\h*)+:#{QUAD}|#{QUAD}|\h*(?::\h*){2,}/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(response:, sending_ip: nil) ⇒ Rejection

Returns a new instance of Rejection.



42
43
44
45
# File 'lib/mailbounce/rejection.rb', line 42

def initialize(response:, sending_ip: nil)
  @response = response.to_s
  @sending_ips = Array(sending_ip).compact
end

Instance Attribute Details

#responseObject (readonly)

Returns the value of attribute response.



40
41
42
# File 'lib/mailbounce/rejection.rb', line 40

def response
  @response
end

Instance Method Details

#about_us?Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/mailbounce/rejection.rb', line 66

def about_us?
  ours.any? { |ip| quoted_addresses.include?(ip) }
end

#categoryObject

Prefer :unknown over a guess — a wrongful retire is worse than extra retries.



48
49
50
51
52
53
54
55
# File 'lib/mailbounce/rejection.rb', line 48

def category
  @category ||=
    if about_us?
      :blocked
    else
      categorized_by_status || categorized_by_text || :unknown
    end
end

#permanent?Boolean

What the server claimed (5xx / class 5), not what we made of it.

Returns:

  • (Boolean)


58
59
60
61
62
63
64
# File 'lib/mailbounce/rejection.rb', line 58

def permanent?
  if status
    status.permanent?
  else
    reply_code.to_s.start_with?(Status::PERMANENT_CLASS)
  end
end

#statusObject



70
71
72
# File 'lib/mailbounce/rejection.rb', line 70

def status
  @status ||= Status.parse(@response)
end