Functions
Functions perform a specific operation on zero or more arguments and produce a value that the surrounding expression can compose with operators or other functions. The general syntax is:
function_name(argument1, argument2, ...)
Arguments may be literal values, context variables, regex capture groups, or the result of another function call. Some functions are synchronous and operate purely on their inputs; others are asynchronous and reach out to a directory, an in-memory store, a SQL database, or the DNS resolver. Both kinds share the same call syntax.
A separate group of forms, documented below as special forms, is recognised directly by the expression parser rather than dispatched through the function table. Regular expressions, system variables, and server metrics fall into this group and have small syntactic restrictions.
Special forms
matches
Evaluates a regular expression against a value and returns true on a match. The first argument must be a string literal; the regex is compiled at parse time. On a successful match, capture groups are exposed to the then branch as positional variables $0 (full match), $1, $2, and so on.
- Arguments: 2 (Regex literal, Value)
- Example:
matches('^([^@]+)@(.+)$', rcpt)matches any syntactically valid recipient and exposes the local part as$1and the domain as$2.
system
Returns a system-level identifier. The argument must be a string literal, selected from a fixed set of names:
-
domain: the default domain, configured bydefaultDomainIdin SystemSettings. -
hostname: the default hostname for the deployment, configured bydefaultHostnamein SystemSettings. -
node_id: the numeric node identifier of the current node in a cluster deployment. -
node_hostname: the operating-system hostname of the current node, overridable with theSTALWART_HOSTNAMEenvironment variable. -
node_role: the role assigned to the current node through theSTALWART_ROLEenvironment variable. -
Arguments: 1 (Name literal)
-
Example:
system('hostname')returns the default hostname used in SMTP greetings.
metric
Returns the current value of a server metric. The argument must be the string literal name of a metric recognised by the server.
- Arguments: 1 (Metric name literal)
- Example:
metric('queue-count') > 10000is true when the outbound queue holds more than ten thousand messages.
Text functions
trim
- Description: Removes whitespace from both ends of a string.
- Arguments: 1 (String)
- Example:
trim(' [email protected] ')returns'[email protected]'.
trim_start
- Description: Removes whitespace from the beginning of a string.
- Arguments: 1 (String)
- Example:
trim_start(' Subject')returns'Subject'.
trim_end
- Description: Removes whitespace from the end of a string.
- Arguments: 1 (String)
- Example:
trim_end('Subject\r\n')returns'Subject'.
len
- Description: Returns the length, in bytes for strings or in elements for arrays.
- Arguments: 1 (String or Array)
- Example:
len(rcpt) > 254flags a recipient that exceeds the SMTP address length limit.
to_lowercase
- Description: Converts a string to lowercase.
- Arguments: 1 (String)
- Example:
to_lowercase(sender_domain)normalises a domain before comparing it against a list.
to_uppercase
- Description: Converts a string to uppercase.
- Arguments: 1 (String)
- Example:
to_uppercase(country)returns'US'whencountryis'us'.
is_lowercase
- Description: Returns
truewhen every alphabetic character in the string is lowercase. - Arguments: 1 (String)
- Example:
is_lowercase('example.org')returnstrue.
is_uppercase
- Description: Returns
truewhen every alphabetic character in the string is uppercase. - Arguments: 1 (String)
- Example:
is_uppercase('HELO')returnstrue.
has_digits
- Description: Returns
truewhen the string contains at least one ASCII digit. - Arguments: 1 (String)
- Example:
has_digits(authenticated_as)detects account names containing digits.
count_chars
- Description: Counts the number of Unicode characters in a string.
- Arguments: 1 (String)
- Example:
count_chars('héllo')returns5.
count_spaces
- Description: Counts whitespace characters in a string.
- Arguments: 1 (String)
- Example:
count_spaces('one two three')returns2.
count_uppercase
- Description: Counts alphabetic characters that are uppercase.
- Arguments: 1 (String)
- Example:
count_uppercase('[email protected]')returns5.
count_lowercase
- Description: Counts alphabetic characters that are lowercase.
- Arguments: 1 (String)
- Example:
count_lowercase('[email protected]')returns7.
contains
- Description: Returns
truewhen the first argument contains the second. If the first argument is an array, element equality is used instead of substring search. - Arguments: 2 (String or Array, Substring or Element)
- Example:
contains(rcpt, '+')detects recipients that use the subaddress convention.
contains_ignore_case
- Description: Like
contains, but compares case-insensitively. - Arguments: 2 (String or Array, Substring or Element)
- Example:
contains_ignore_case(sender_domain, 'example')matchesEXAMPLE.org.
eq_ignore_case
- Description: Compares two strings for equality, ignoring ASCII case.
- Arguments: 2 (String, String)
- Example:
eq_ignore_case(listener, 'SMTP')is true for both'smtp'and'SMTP'.
starts_with
- Description: Returns
truewhen the first string starts with the second. - Arguments: 2 (String, Prefix)
- Example:
starts_with(authenticated_as, 'svc-')matches service accounts prefixed withsvc-.
ends_with
- Description: Returns
truewhen the first string ends with the second. - Arguments: 2 (String, Suffix)
- Example:
ends_with(sender_domain, '.example.org')matches every subdomain ofexample.org.
strip_prefix
- Description: Removes the given prefix from the start of the string. Returns the empty string when the prefix is not present.
- Arguments: 2 (String, Prefix)
- Example:
strip_prefix(authenticated_as, 'svc-')yields'backup'when called on'svc-backup'.
strip_suffix
- Description: Removes the given suffix from the end of the string. Returns the empty string when the suffix is not present.
- Arguments: 2 (String, Suffix)
- Example:
strip_suffix(rcpt_domain, '.example.org')yields the customer tenant name.
substring
- Description: Extracts a substring by character position. The second argument is the zero-based start index; the third is the number of characters to take.
- Arguments: 3 (String, Start, Count)
- Example:
substring(remote_ip, 0, 3)returns'192'for the IP'192.0.2.1'.
lines
- Description: Splits a string into an array on newline boundaries.
- Arguments: 1 (String)
- Example:
lines(key_get('', 'blocklist'))returns the entries of a newline-delimited blocklist stored under the keyblocklist.
split
- Description: Splits a string on every occurrence of a delimiter and returns the resulting array.
- Arguments: 2 (String, Delimiter)
- Example:
split('a,b,c', ',')returns['a', 'b', 'c'].
rsplit
- Description: Like
split, but the resulting array is ordered from right to left. - Arguments: 2 (String, Delimiter)
- Example:
rsplit('mx1.example.org', '.')returns['org', 'example', 'mx1'].
split_once
- Description: Splits a string at the first occurrence of the delimiter and returns the two resulting parts as a two-element array. Returns the empty string when the delimiter is not found.
- Arguments: 2 (String, Delimiter)
- Example:
split_once(rcpt, '@')returns['user', 'example.org']for the recipient'[email protected]'.
rsplit_once
- Description: Splits a string at the last occurrence of the delimiter and returns the two resulting parts as a two-element array.
- Arguments: 2 (String, Delimiter)
- Example:
rsplit_once('[email protected]', '@')returns['user+tag', 'example.org'].
split_n
- Description: Splits a string at most
ntimes. The final element contains any remaining input, including further delimiters. - Arguments: 3 (String, Delimiter, Max Splits)
- Example:
split_n('a,b,c,d', ',', 2)returns['a', 'b', 'c,d'].
split_words
- Description: Splits a string on whitespace and returns only the tokens consisting entirely of alphanumeric characters.
- Arguments: 1 (String)
- Example:
split_words('Hello, world! 42')returns['42']; the punctuated tokens are discarded.
hash
- Description: Computes a hash of the input string and returns it as a lowercase hexadecimal string. Supported algorithms are
md5,sha1,sha256, andsha512. Any other algorithm name produces the empty string. - Arguments: 2 (String, Algorithm)
- Example:
hash(sender, 'sha256')produces a stable identifier for the sender address, useful for bucketing and rate-limiting.
Array functions
count
- Description: Returns the number of elements in an array. For non-array values, returns
1when the value is non-empty and0otherwise. - Arguments: 1 (Array)
- Example:
count(recipients) > 100flags messages addressed to more than a hundred recipients.
sort
- Description: Returns a sorted copy of the input array. The second argument is
truefor ascending order andfalsefor descending. - Arguments: 2 (Array, Ascending)
- Example:
sort(['z', 'a', 'b'], true)returns['a', 'b', 'z']; the same call withfalsereturns['z', 'b', 'a'].
dedup
- Description: Returns a copy of the array with duplicate elements removed, preserving the original order.
- Arguments: 1 (Array)
- Example:
dedup(['a', 'b', 'a'])returns['a', 'b'].
winnow
- Description: Returns a copy of the array with empty elements removed.
- Arguments: 1 (Array)
- Example:
winnow(['a', '', 'b', ''])returns['a', 'b'].
is_intersect
- Description: Returns
truewhen the two arrays share at least one element. When one of the arguments is a scalar value, checks whether the value is contained in the other array. - Arguments: 2 (Array, Array)
- Example:
is_intersect(recipients, ['[email protected]', '[email protected]'])flags messages that include a role address.
Email functions
is_email
- Description: Returns
truewhen the input string is a syntactically valid email address. The check requires exactly one unquoted@, at least one character in the local part, and at least one dot-separated domain label. - Arguments: 1 (String)
- Example:
is_email(rcpt)can be used to reject malformed addresses early in a pipeline.
email_part
- Description: Extracts either the local part or the domain of an email address. The second argument must be the literal string
'local'or'domain'; any other value returns the empty string. - Arguments: 2 (Email, Part)
- Example:
email_part(sender, 'domain')returns'example.org'whensenderis'[email protected]'.
Directory functions
is_local_domain
- Description: Returns
truewhen the domain is registered in the server's directory. Uses the default directory. - Arguments: 1 (Domain)
- Example:
is_local_domain(rcpt_domain)is the usual test for a recipient that this server is authoritative for.
is_local_address
- Description: Returns
truewhen the given email address resolves to a local account or alias. Uses the default directory. - Arguments: 1 (Email address)
- Example:
is_local_address(rcpt)distinguishes deliverable recipients from relay recipients.
In-memory store functions
These functions target an in-memory store identified by its ID. An empty string selects the default in-memory store configured on the server.
key_get
- Description: Returns the value associated with a key, or the empty string when the key does not exist.
- Arguments: 2 (Store ID, Key)
- Example:
key_get('', 'greeting')fetches the value of thegreetingkey from the default in-memory store.
key_exists
- Description: Returns
truewhen the key exists. - Arguments: 2 (Store ID, Key)
- Example:
key_exists('blocklist', remote_ip)is true when the IP is present in theblocklistin-memory store.
key_set
- Description: Stores the given value under the given key, creating the key if necessary. Returns
trueon success. - Arguments: 3 (Store ID, Key, Value)
- Example:
key_set('', 'last_sender_' + authenticated_as, sender)records the most recent envelope sender per authenticated account.
counter_get
- Description: Returns the current value of a counter, or
0when the counter does not exist. - Arguments: 2 (Store ID, Counter Name)
- Example:
counter_get('', 'rcpt-' + authenticated_as)returns the number of recipients seen for this account.
counter_incr
- Description: Increments a counter by the given amount, creating it if necessary. Returns the new counter value.
- Arguments: 3 (Store ID, Counter Name, Increment)
- Example:
counter_incr('', 'rcpt-' + authenticated_as, 1) > 1000increments a per-account recipient counter and triggers when it exceeds a threshold.
SQL store functions
sql_query
- Description: Runs a SQL statement against a data store of type SQL. The third argument binds parameters to the query's
?placeholders, in order. ASELECTreturning exactly one row and one column returns the scalar value; aSELECTreturning a single row with multiple columns returns that row as an array; multiple rows return an array of arrays. A non-SELECTstatement returns the number of affected rows. - Arguments: 3 (Store ID, Query, Parameters)
- Example:
sql_query('crm', 'SELECT quota FROM users WHERE address = ?', [rcpt])retrieves the recipient's quota from thecrmSQL store.
DNS functions
dns_query
- Description: Performs a DNS query for the given name and record type and returns the result. Supported record types are
ipv4,ipv6,ip(IPv4 with IPv6 fallback),mx,txt, andptr. Forptr, the first argument must parse as an IP address. Address andmxqueries return arrays;txtreturns a single concatenated string. - Arguments: 2 (Name, Record Type)
- Example:
contains(dns_query(ip_reverse_name(remote_ip) + '.zen.spamhaus.org', 'ipv4'), '127.0.0.2')tests whetherremote_ipis listed in the Spamhaus DNSBL.ip_reverse_nameproduces the reversed form required by the DNSBL protocol, and the two strings are concatenated into the query name.
Miscellaneous functions
is_empty
- Description: Returns
truewhen the value is an empty string or an empty array. Numeric and constant values are never considered empty. - Arguments: 1 (Value)
- Example:
is_empty(authenticated_as)is true for unauthenticated sessions.
is_number
- Description: Returns
truewhen the value is an integer or a float. A numeric string returnsfalse; the check is on the runtime type, not the content. - Arguments: 1 (Value)
- Example:
is_number(priority)distinguishes set numeric priorities from unset string values.
is_ip_addr
- Description: Returns
truewhen the string parses as either an IPv4 or an IPv6 address. - Arguments: 1 (String)
- Example:
is_ip_addr(key_get('', 'next_hop'))validates a hop read from configuration.
is_ipv4_addr
- Description: Returns
truewhen the string parses as an IPv4 address. - Arguments: 1 (String)
- Example:
is_ipv4_addr(remote_ip)gates a rule that only applies to IPv4 clients.
is_ipv6_addr
- Description: Returns
truewhen the string parses as an IPv6 address. - Arguments: 1 (String)
- Example:
is_ipv6_addr(remote_ip)gates a rule that only applies to IPv6 clients.
ip_reverse_name
- Description: Returns the reverse-DNS form of an IP address, suitable for composing DNSBL lookups. For IPv4 the octets are reversed; for IPv6 each nibble is reversed and separated by dots.
- Arguments: 1 (IP Address)
- Example:
ip_reverse_name(remote_ip) + '.zen.spamhaus.org'builds the query name used withdns_queryto consult the Spamhaus DNSBL.
if_then
- Description: Returns the second argument when the first is truthy, otherwise the third.
- Arguments: 3 (Condition, When True, When False)
- Example:
if_then(is_tls, 'tls', 'plain') + '-' + listenerbuilds a channel tag such as'tls-submission'or'plain-smtp'.