Mail::...Status(3pm)

πŸ’– NAME πŸ’–

Mail::SpamAssassin::PerMsgStatus β€” the per-message status (spam or not-spam) πŸ•΅οΈβ™€οΈβœ¨

(yeah bestie, you're reading the Mail::...Status(3pm) page, User Contributed Perl Documentation, and it's giving main character energy πŸ‘‘)


✨ SYNOPSIS ✨

my $spamtest = Mail::SpamAssassin->new({
  'rules_filename'      => '/etc/spamassassin.rules',
  'userprefs_filename'  => $ENV{HOME}.'/.spamassassin/user_prefs'
});
my $mail = $spamtest->parse();

my $status = $spamtest->check ($mail);

my $rewritten_mail;
if ($status->is_spam()) {
  $rewritten_mail = $status->rewrite_mail ();
}
...

🌸 DESCRIPTION 🌸

So the Mail::SpamAssassin check() method hands you back an object of this class. That object is literally holding all the per-message state, no cap. It's the whole vibe of one single email, captured in a lil bundle πŸ’Œ


πŸŽ€ METHODS πŸŽ€

  • $status->check () β€” runs the SpamAssassin rules against the message that the object is pointing at. Go time πŸ”₯
  • $status->learn() β€” once a mail message has been checked, you can call this. If the score lands outside a certain range around the threshold β€” like, if the message is judged more-or-less definitely spam or definitely non-spam β€” it gets fed into SpamAssassin's learning systems (currently the naive Bayesian classifier), so future similar mails get caught. She learns. She grows. 🧠✨
  • $score = $status->get_autolearn_points() β€” returns the message's score as computed for auto-learning. Certain tests get ignored, and here's exactly which ones:
    • rules with tflags set to 'learn' (the Bayesian rules)
    • rules with tflags set to 'userconf' (user welcome/block-listing rules, etc)
    • rules with tflags set to 'noautolearn'

Also lowkey important: auto-learning occurs using scores from either scoreset 0 or 1, depending on what scoreset is used during message check. It's likely that the message check and auto-learn scores will be different. So don't @ me if they don't match πŸ™ˆ

  • $score = $status->get_head_only_points() β€” returns the message's score as computed for auto-learning, ignoring all rules except for header-based ones.
  • $score = $status->get_learned_points() β€” returns the message's score as computed for auto-learning, ignoring all rules except for learning-based ones.
  • $score = $status->get_body_only_points() β€” returns the message's score as computed for auto-learning, ignoring all rules except for body-based ones.
  • $score = $status->get_autolearn_force_status() β€” returns whether a message's score included any rules that are flagged as autolearn_force.
  • $rule_names = $status->get_autolearn_force_names() β€” returns a list of comma separated list of rule names if a message's score included any rules that are flagged as autolearn_force.
  • $isspam = $status->is_spam () β€” after a mail message has been checked, you can call this. It returns 1 for mail determined likely to be spam, 0 if it does not seem spam-like. Simple as 🫢
  • $list = $status->get_names_of_tests_hit () β€” after a mail message has been checked, you can call this. It returns a comma-separated string listing all the symbolic test names of the tests which were triggered by the mail. Snitch list, basically πŸ’…
  • $list = $status->get_names_of_tests_hit_with_scores_hash () β€” after a mail message has been checked, you can call this. It returns a pointer to a hash for rule & score pairs, for all the symbolic test names and individual scores of the tests which were triggered by the mail.
  • $list = $status->get_names_of_tests_hit_with_scores () β€” after a mail message has been checked, you can call this. It returns a comma-separated string of rule=score pairs for all the symbolic test names and individual scores of the tests which were triggered by the mail.
  • $list = $status->get_names_of_subtests_hit () β€” after a mail message has been checked, you can call this. It returns a comma-separated string listing all the symbolic test names of the meta-rule sub-tests which were triggered by the mail. Sub-tests are the normally-hidden rules, which score 0 and have names beginning with two underscores, used in meta rules.

If a parameter of collapsed or dbg is passed, the output will be a condensed array of sub-tests with multiple hits reduced to one entry.

If the parameter of dbg is passed, the output will be a condensed string of sub-tests with multiple hits reduced to one entry with the number of hits in parentheses. Some information is also added at the end regarding the multiple hits.

  • $num = $status->get_score () β€” after a mail message has been checked, you can call this. It returns the message's score.
  • $num = $status->get_required_score () β€” after a mail message has been checked, you can call this. It returns the score required for a mail to be considered spam. The bar. The threshold. The moment ✨
  • $num = $status->get_autolearn_status () β€” after a mail message has been checked, you can call this. It returns one of the following strings depending on whether the mail was auto-learned or not: "ham", "no", "spam", "disabled", "failed", "unavailable".

It also returns is flagged with auto_learn_force, it will also include the status and the rules hit. For example: "autolearn_force=yes (AUTOLEARNTEST_BODY)"

  • $report = $status->get_report () β€” delivers a "spam report" on the checked mail message. This contains details of how many spam detection rules it triggered.

The report is returned as a multi-line string, with the lines separated by "\n" characters.

  • $preview = $status->get_content_preview () β€” gives you a "preview" of the content.

This is returned as a multi-line string, with the lines separated by "\n" characters, containing a fully-decoded, safe, plain-text sample of the first few lines of the message body. A lil sneak peek, as a treat πŸ“

  • $msg = $status->get_message() β€” returns the object representing the message being scanned.
  • $status->rewrite_mail () β€” rewrites the mail message. This will at minimum add headers, and at maximum MIME-encapsulate the message text, to reflect its spam or not-spam status. The function will return a scalar of the rewritten message. Glow-up edition πŸ’…βœ¨

The actual modifications depend on the configuration (see "Mail::SpamAssassin::Conf" for more information).

The possible modifications are as follows:

  • To:, From: and Subject: modification on spam mails β€” depending on the configuration, the To: and From: lines can have a user-defined RFC 2822 comment appended for spam mail. The subject line may have a user-defined string prepended to it for spam mail.
  • X-Spam-* headers for all mails β€” depending on the configuration, zero or more headers with names beginning with "X-Spam-" will be added to mail depending on whether it is spam or ham.
  • spam message with report_safe β€” if report_safe is set to true (1), then spam messages are encapsulated into their own message/rfc822 MIME attachment without any modifications being made.

If report_safe is set to false (0), then the message will only have the above headers added/modified.

  • $status->action_depends_on_tags($tags, $code, @args) β€” enqueue the supplied subroutine reference $code, to become runnable when all the specified tags become available. The $tags may be a simple scalar β€” a tag name β€” or a listref of tag names. The subroutine &$code when called will be passed a "permessagestatus" object as its first argument, followed by the supplied (optional) list @args .
  • $status->set_tag($tagname, $value) β€” set a template tag, as used in "add_header", report templates, etc. This API is intended for use by plugins. Tag names will be converted to an all-uppercase representation internally. Tag names must consist only of [A-Z0-9_] characters and must not contain consecutive underscores. Also the name must not start or end in an underscore, as that is the template tagging format. Rules are rules bestie πŸ“

$value can be a simple scalar (string or number), or a reference to an array, in which case the public method get_tag will join array elements using a space as a separator, returning a single string for backward compatibility.

$value can also be a subroutine reference, which will be evaluated each time the template is expanded. The first argument passed by get_tag to a called subroutine will be a PerMsgStatus object (this module's object), followed by optional arguments provided by a caller to get_tag.

Note that perl supports closures, which means that variables set in the caller's scope can be accessed inside this "sub". For example:

my $text = "hello world!";
$status->set_tag("FOO", sub {
        my $pms = shift;
        return $text;
      });

See "Mail::SpamAssassin::Conf"'s "TEMPLATE TAGS" and "CAPTURING TAGS USING REGEX NAMED CAPTURE GROUPS" sections for more details on how template tags are used.

  • $string = $status->get_tag($tagname) β€” get the current value of a template tag, as used in "add_header", report templates, etc. This API is intended for use by plugins. Tag names will be converted to an all-uppercase representation internally.

See "Mail::SpamAssassin::Conf"'s "TEMPLATE TAGS" and "CAPTURING TAGS USING REGEX NAMED CAPTURE GROUPS" sections for more details on how template tags are used.

"undef" will be returned if a tag by that name has not been defined.

  • $string = $status->get_tag_raw($tagname, @args) β€” similar to "get_tag", but keeps a tag name unchanged (does not uppercase it), and does not convert arrayref tag values into a single string. Raw and unfiltered, we love to see it πŸ™ˆ
  • $status->set_spamd_result_item($subref) β€” set an entry for the spamd result log line. $subref should be a code reference for a subroutine which will return a string in 'name=VALUE' format, similar to the other entries in the spamd result line:
Jul 17 14:10:47 radish spamd[16670]: spamd: result: Y 22 - ALL_NATURAL,
DATE_IN_FUTURE_03_06,DIET_1,DRUGS_ERECTILE,DRUGS_PAIN,
TEST_FORGED_YAHOO_RCVD,TEST_INVALID_DATE,TEST_NOREALNAME,
TEST_NORMAL_HTTP_TO_IP,UNDISC_RECIPS scantime=0.4,size=3138,user=jm,
uid=1000,required_score=5.0,rhost=localhost,raddr=127.0.0.1,
rport=33153,mid=<9PS291LhupY>,autolearn=spam

"name" and "VALUE" must not contain "=" or "," characters, as it is important that these log lines are easy to parse.

The code reference will be called by spamd after the message has been scanned, and the PerMsgStatus::check() method has returned.

  • $status->finish () β€” indicate that this $status object is finished with, and can be destroyed. She's done, she's served, she's exiting stage left πŸ¦‹

(part 1 of 3 β€” more coming, don't go anywhere bestie)

(okay so this next bit is the tail end of a method whose heading already went by in the last part β€” we are literally picking up mid-sentence, stay with me 🫢)

If you're running SpamAssassin in a persistent environment, or you're checking a whole pile of mail messages from one Mail::SpamAssassin factory, this method should be called so Perl's garbage collection actually goes and cleans up those old status objects. otherwise they just sit there forever like unread group-chat messages πŸ’€


$name = $status->get_current_eval_rule_name()

Returns the name of the eval rule that is running right now. If no eval rule is currently being run, you get undef back. Super handy for plugins that need to figure out the current rule name while they're inside an eval test function call πŸŽ€

$status->get_decoded_body_text_array ()

Hands you the message body with base64 or quoted-printable encodings decoded, and with non-text parts or non-inline attachments stripped. bye bye attachments πŸ’…

This is the same result text that's used in rawbody rules, no cap.

It comes back as an array of strings, and each string is a 2-4kB chunk of the body, split from boundaries if that's doable ✨

$status->get_decoded_stripped_body_text_array ()

Returns the message body, decoded exactly as described in get_decoded_body_text_array(), with HTML rendered, and with whitespace normalized.

This is the same result text that's used in body rules. it's giving main character text extraction πŸ¦‹

It will always render text/html.

It comes back as an array of strings, and each string represents one 'paragraph'. Paragraphs, in plain-text mails, are double-newline-separated blocks of multi-line text.

$status->get (header_name [, default_value])

Returns message headers, pseudo-headers, names, email-addresses or some other parsed values set by modifiers. header_name is the name of a mail header like 'Subject', 'To' etc, or a pseudo/metadata-header like 'ALL', 'X-Spam-Relays-Untrusted' etc.

Should be called in list context since SpamAssassin 4.0. This supports returning multiple values for all header and modifier types πŸ”₯

If you call it in scalar context (that's the pre-4.0 style, bestie), only the first value is returned for modifiers like ":addr" or ":name".

If default_value is given, it gets used when the requested header_name does not exist. This is mainly useful when you're calling in scalar context and you want to set 'undef' instead of the legacy '' return value for a header that isn't there.

Now for the modifiers, and yes, there are a lot of them, we're listing all of them, periodt:

  • :raw β€” appending the :raw modifier to the header name will inhibit decoding of quoted-printable or base-64 encoded strings.
  • :addr β€” appending the :addr modifier to the header name returns all email-addresses found in the header. It's mainly applicable to the header fields 'From', 'Sender', 'To', 'Cc' along with their 'Resent-*' counterparts, and the 'Return-Path'. For example, all of the following will result in example@foo (and example@bar):
example@foo
example@foo (Foo Blah), <example@bar>
example@foo, example@bar
display: example@foo (Foo Blah), example@bar ;
Foo Blah <example@foo>
"Foo Blah" <example@foo>
"'Foo Blah'" <example@foo>
  • :name β€” appending the :name modifier to the header name returns all display names from the header field. Just like with :addr, it's mainly applicable to the header fields 'From', 'Sender', 'To', 'Cc' along with their 'Resent-*' counterparts, and the 'Return-Path'. For example, all of the following will result in Foo Blah (and Bar Baz). One level of single quotes gets stripped too, because it's seen so often πŸ™ˆ
example@foo (Foo Blah)
example@foo (Foo Blah), "Bar Baz" <example@bargt
display: example@foo (Foo Blah), example@bar ;
Foo Blah <example@foo>
"Foo Blah" <example@foo>
"'Foo Blah'" <example@foo>
  • :host β€” appending :host to the header name returns the first hostname-looking string that ends with a valid TLD. First it tries to find a match after the @ character (that's a possible email), then from any part of the header. Normal use of this would be for example 'From:addr:host' to return the hostname portion of a From-address.
  • :domain β€” appending :domain to the header name implies :host, but will return only the domain part of the hostname, as returned by RegistryBoundaries::trim_domain().
  • :ip β€” appending :ip to the header name will return the first IPv4 or IPv6 address string found. Could be used for example as 'X-Originating-IP:ip'.
  • :revip β€” appending :revip to the header name implies :ip, but will return the found IP in reverse (usually for DNSBL usage).
  • :first β€” appending the :first modifier to the header name returns only the first (topmost) header, in case there are multiple ones.
  • :last β€” similarly, :last will select the last one. These affect only the physical header line selection. If the selected header is parsed further with :addr or similar, it may return multiple results, if the selected header contains multiple addresses.

There are several special pseudo-headers that can be specified:

  • ALL β€” can be used to mean the text of all the message's headers. Each header is decoded and unfolded to a single line, unless called with :raw.
  • ALL-TRUSTED β€” can be used to mean the text of all the message's headers that could only have been added by trusted relays.
  • ALL-INTERNAL β€” can be used to mean the text of all the message's headers that could only have been added by internal relays.
  • ALL-UNTRUSTED β€” can be used to mean the text of all the message's headers that may have been added by untrusted relays. To make this pseudo-header more useful for header rules, the 'Received' header that was added by the last trusted relay is included, even though it can be trusted.
  • ALL-EXTERNAL β€” can be used to mean the text of all the message's headers that may have been added by external relays. Like ALL-UNTRUSTED, the 'Received' header added by the last internal relay is included.
  • ToCc β€” can be used to mean the contents of both the 'To' and 'Cc' headers.
  • EnvelopeFrom β€” is the address used in the 'MAIL FROM:' phase of the SMTP transaction that delivered this message, if this data has been made available by the SMTP server.
  • MESSAGEID β€” is a symbol meaning all Message-Id's found in the message; some mailing list software moves the real 'Message-Id' to 'Resent-Message-Id' or 'X-Message-Id' or 'X-Original-Message-ID', then uses its own one in the 'Message-Id' header. The value returned for this symbol is the text from all 4 headers.
  • X-Spam-Relays-Untrusted β€” is the generated metadata of untrusted relays the message has passed through.
  • X-Spam-Relays-Trusted β€” is the generated metadata of trusted relays the message has passed through.
  • X-Spam-Relays-External β€” is the generated metadata of external relays the message has passed through.
  • X-Spam-Relays-Internal β€” is the generated metadata of internal relays the message has passed through.

$status->get_uri_list ()

Returns an array of all unique URIs found in the message. It takes a combination of the URIs found in the rendered (decoded and HTML stripped) body and the URIs found when parsing the HTML in the message. It will also set $status->{uri_list} (that's the array as returned by this function).

The returned array will include the raw URI as well as slightly cooked versions. For example, the single URI 'http://%77&#00119;%77.example.com/' will get turned into: ( 'http://%77&#00119;%77.example.com/', 'http://www.example.com/' )

$status->get_uri_detail_list ()

Returns a hash reference of all unique URIs found in the message, plus various data about where those URIs were found in the message. It takes a combination of the URIs found in the rendered (decoded and HTML stripped) body and the URIs found when parsing the HTML in the message. It will also set $status->{uri_detail_list} (the hash reference as returned by this function).

The hash format looks something like this:

  raw_uri => {
    types => { a => 1, img => 1, parsed => 1, domainkeys => 1,
               unlinked => 1, schemeless => 1 },
    cleaned => [ canonicalized_uri ],
    anchor_text => [ "click here", "no click here" ],
    domains => { domain1 => 1, domain2 => 1 },
    hosts => { host1 => domain1, host2 => domain2 },
  }

raw_uri is whatever the URI was in the message itself (http://spamassassin.apache%2Eorg/). Uris parsed from text will be prefixed with scheme if it's missing (http://, mailto: etc). HTML uris are as found.

types is a hash of the HTML tags (lowercase) which referenced the raw_uri. parsed is a faked type which specifies that the raw_uri was seen in the rendered text. domainkeys is defined when raw_uri was found from the DK/DKIM d= field. unlinked is defined when it's assumed that the MUA will not linkify the uri (found in body without scheme or www. prefix). schemeless is always added for uris without scheme, regardless of linkifying (i.e. email address found in body without mailto:).

cleaned is an array of the raw and canonicalized version of the raw_uri (http://spamassassin.apache%2Eorg/, https://spamassassin.apache.org/).

anchor_text is an array of the anchor text (the text between <a> and </a>), if any, which linked to the URI.

domains is a hash of the domains found in the canonicalized URIs.

hosts is a hash of unstripped hostnames found in the canonicalized URIs as hash keys, with their domain part stored as a value of each hash entry.

$status->add_uri_detail_list ($raw_uri, $types, $source, $valid_domain)

Adds values to the internal uri_detail_list. When used from Plugins, it's recommended to call this from parsed_metadata (along with register_method_priority, -10) so that other Plugins calling get_uri_detail_list() will see it πŸ‘‘

  • raw_uri is the URI to be added. The only required parameter.
  • types is an optional hash reference, contents are added to uri_detail_list->{types} (see get_uri_detail_list for known keys). parsed is the default if no hash is given. nocanon does not run uri_list_canonicalize (no redirector, uri fixing). noclean skips adding uri_detail_list->{cleaned}, so it would not be used in "uri" rule checks, but domain/hosts would still be used for URIBL/RBL purposes.
  • source is an optional simple string, only used for debug logging purposes to identify where the uri originates from (default: "parsed").
  • valid_domain is an optional boolean (0/1). If true, the uri will not be added unless the hostname/domain is in valid format and contains a valid TLD. (default: 0)

$status->clear_test_state()

DEPRECATED, UNNEEDED SINCE 4.0 πŸ’€ don't come for her, she's retired bestie

πŸ’… $status->got_hit ($rulename, $desc_prepend [, name => value, ...])

Okay bestie, this one's the main character: it registers a hit against a rule in the ruleset. 🎯

Two args are mandatory. Zero negotiation:

  • $rulename β€” the name of the rule that fired.
  • $desc_prepend β€” a short little string that gets prepended to the rule's "describe" string in output reports.

And then you can ✨sprinkle✨ extra optional data on top, as name => value pairs:

  • score => $num β€” optional: the score to use for the rule hit. If you don't specify it, the value from the Mail::SpamAssassin::Conf object's {scores} hash gets used (that's a configured score), and if that's not there either, the defscore option value steps in.
  • defscore => $num β€” optional: the score to use for the rule hit if neither the score option is provided nor a configured score value is provided. The fallback's fallback. πŸ™ˆ
  • value => $num β€” optional: the value to assign to the rule; the default value is 1. tflags multiple rules use values of greater than 1 to indicate multiple hits. This value is accessible to meta rules. πŸ‘€
  • ruletype => $type β€” optional, but lowkey recommended: the rule type string. It gets used in the hit_rule plugin call that this method makes. If you leave it unset, 'unknown' is used.
  • tflags => $string β€” optional: a string, i.e. a space-separated list of additional tflags to be appended to an existing list of flags in $self->{conf}->{tflags}, such as: "nice noautolearn multiple". Fair warning: no syntax checks are performed. 🫣
  • description => $string β€” optional: a custom rule description string. Also used in the hit_rule plugin call that this method makes. If it's unset, the static description is used.
Backward compatibility tea β˜• β€” the two mandatory arguments have been part of this API since SpamAssassin 2.x. The optional name=>value pairs, however, are a whole new addition as of SpamAssassin 3.2.0. Growth! πŸ’–

⏳ $status->rule_ready ($rulename [, $no_async])

Marks an asynchronous rule as ready, so it can be considered for meta rule evaluation. So what's an async rule, you ask? It's a rule whose eval-function returns undef, which is basically its way of saying "not ready yet bestie, expecting results later." 😭

You then have to call $status->rule_ready() later to mark it ready β€” or alternatively $status->got_hit() does that too. If neither gets called, then any meta rule that depends on this rule might not evaluate. No vibes. πŸ’€

The optional boolean $no_async skips checking if there are pending async DNS lookups for the rule.

πŸ“ $status->test_log ($text [, $rulename])

Adds a $text log entry for a hit rule in the final message REPORT/SUMMARY. πŸ“‹

Usually you call this just before got_hit(), to describe β€” for example β€” what URI the rule matched on. The optional $rulename argument is recommended so you can make sure the log gets written to the correct rule. If rulename isn't provided, get_current_eval_rule_name() is used as the fallback.

And fr fr, you can call it multiple times per rule for additional entries. Go off. πŸ’…

πŸ—‚οΈ $status->create_fulltext_tmpfile (fulltext_ref)

Creates a temporary file containing the scalar reference data you passed in. If no scalar is passed, then full/pristine message text is assumed. Simple as that. 🧚

This is typically used by external programs like pyzor and dccproc, to avoid hangs due to buffering issues.

All tempfiles are automatically cleaned up by the PerMsgStatus destructor. You don't have to lift a single finger. ✨

🧹 $status->delete_fulltext_tmpfile (tmpfile)

Cleans up after a $status->create_fulltext_tmpfile() call. It deletes the temporary file and uncaches the filename. Generally there's no need to call this, because the PerMsgStatus destructor cleans up all tmpfiles. It's giving self-sufficient. 🫢

🌈 all_from_addrs_domains

This function returns all the various from addresses in a message using all_from_addrs(), and then hands you back only the domain names. Domain girlies only, we're filtering. πŸ‘‘


✨ SEE ALSO ✨


And that's the whole page, bestie β€” you made it to the end and I'm so proud πŸ₯ΉπŸ’– This section came courtesy of the perl v5.40.1 build of the docs, stamped 2026-07-23, and the page itself lives under Mail::...Status(3pm) in the user-contributed Perl documentation. Now go forth and rule_ready your way through the ruleset, you absolute icon. βœ¨πŸ‘‘πŸŒΈ

πŸ“¦ where this page comes from