MAILDROPFILTER(7)

hey bestie!! welcome to maildropfilter(7) ๐Ÿ’… โ€” authored by S. Varshavchik, translated into group-chat by me, your resident Unix girlie ๐Ÿซถ. this is the full vibe guide to maildrop's filtering language, and yeah it's long, so hydrate ๐Ÿง‹โœจ

๐ŸŽ€ NAME ๐ŸŽ€

maildropfilter โ€” it's literally maildrop's filtering language ๐ŸŒธ no cap.

โœจ SYNOPSIS โœจ

/etc/maildroprc, $HOME/.mailfilter, $HOME/.mailfilters/*, and friends...

that's the whole squad of places these filter files live ๐Ÿฆ‹


๐Ÿ’– DESCRIPTION ๐Ÿ’–

okay so this page describes the language maildrop uses to filter E-mail messages ๐Ÿ“ฌ. the mail filtering instructions get read in from a file. the language is loosely structured โ€” it's based on pattern matching ๐Ÿงš. it's got its own distinct lexical and syntactical structure, and it's very similar to Perl'sโ€ฆ but and this is the important part, bestie โ€” it is NOT Perl, and it is very different from Perl in certain cases ๐Ÿ’€. do not come for me if you assume Perl things. do not.

If the filtering instructions do not exist, maildrop just delivers the message to the default mailbox without doing any additional processing at all โ€” which makes it completely indistinguishable from your usual mail delivery agent ๐ŸŒˆ.

and now the warning that matters: maildrop reads and parses the filter file before doing anything ๐Ÿ‘‘. so if there are any errors at all, maildrop prints an error message and terminates with the exit code set to EX_TEMPFAIL ๐Ÿ˜ญ. a compliant mail transport agent should re-queue the message for a later delivery attempt. hopefully, most simple syntax errors will not cause mail to be bounced back if the error is caught and fixed quickly ๐Ÿ™ โ€” the "hopefully" is doing a lot of heavy lifting there, iykyk.

๐ŸŒฑ Environment ๐ŸŒฑ

maildrop uses variables to access and manipulate messages. variables are arbitrary text, and you access them by referring to the name of the variable โ€” like HOME, or DEFAULT ๐Ÿ’œ. you put text into a variable with an assignment statement, like this:

FILE="IN.junk"

this statement puts the text "IN.junk" (without the quotes) into a variable whose name is FILE โœจ. later, the contents of a variable are accessed by using the $ symbol and the name for the variable. for example:

to $FILE

this will deliver the current message to the mailbox file (or a maildir directory) named "IN.junk" ๐Ÿ“ฅ.

now, maildrop initially creates variables from the environment variables of the operating system โ€” UNLESS maildrop runs in delivery mode ๐Ÿšจ. each operating system environment variable becomes a maildrop variable. When running in delivery mode, maildrop does not import the environment, for security reasons ๐Ÿ›ก๏ธ โ€” except for the environment variables that define the process locale (LANG, LANGUAGE, and LC_*), which are still imported ๐Ÿฆ‹.

in all cases maildrop resets the following variables to their default values: HOME, DEFAULT, SHELL, PATH, LOCKEXT, LOCKREFRESH, LOCKSLEEP, LOCKTIMEOUT, MAILDIRQUOTA, SENDMAIL and LOGNAME ๐ŸŽ€.

there's one exception to this rule, and it applies to the version of maildrop that comes with the Courier mail server[1] ๐Ÿ‘‘. the following does not apply to the standalone version of maildrop: when running in delivery mode, if the -d flag was not used, or if it specifies the same userid as the one that's running maildrop โ€” then the following variables are automatically imported from the environment: HOME, SHELL, LOGNAME and MAILDIRQUOTA ๐ŸŒธ. these environment variables are initialized by the Courier mail server prior to running maildrop. additionally, the initial value for the DEFAULT maildrop variable is imported from the MAILDROPDEFAULT environment variable ๐Ÿ’œ. this is because the Courier mail server overloads the DEFAULT environment variable to store the defaulted portion of the local mailbox address. see the dot-courier(5)[2] man page in the Courier mail server distribution for the deets ๐Ÿ“–.

you can get the Courier mail server's DEFAULT value by using the import command. note, however, that this will clobber the old contents of DEFAULT, which is probably not what you want ๐Ÿ˜ญ. the right way to do this would be something like this:

SAVEDEFAULT=$DEFAULT
import DEFAULT
LOCALDEFAULT=$DEFAULT
DEFAULT=$SAVEDEFAULT

see?? save it first, bestie. character development ๐Ÿซถ.

all internal variables are exported back as environment variables when maildrop runs an external command ๐Ÿƒโ™€๏ธ. changes to internal variables, made by the filter file, are reflected in the exported environment โœจ.

๐Ÿ“ Lexical structure ๐Ÿ“

most whitespace is generally ignored ๐Ÿ˜Œ. the # character introduces a comment running to the end of the line, which is also ignored ๐Ÿ’…. and unlike other mail filters, maildrop parses the filter file before taking any action with the message. if there are syntax errors in the file, maildrop displays an error message, and returns EX_TEMPFAIL ๐Ÿšจ. that should cause the mail message to remain in the queue and, hopefully, allow the problem to be corrected โ€” without bouncing any mail ๐Ÿ™.

๐ŸŽ€ Note ๐ŸŽ€ in maildrop, the end of line is a lexical token. so in order to continue a long statement on the next line, you terminate the line with a backslash character โญ.

๐Ÿ”ค Literal text ๐Ÿ”ค

literal text in the maildrop filtering language is surrounded by either single or double quotes โœจ. in order to enter a single quote into a text literal surrounded by single quotes, or a double quote into a literal surrounded by double quotes, prefix it with a backslash character ๐Ÿฆ‹. use two backslash characters to enter one backslash character in the text literal ๐Ÿ’œ.

๐Ÿ“ Note ๐Ÿ“ a backslash followed by either a backslash, or a matching quote, is the only situation where the backslash character is actually removed, leaving only the following character in the actual text literal ๐Ÿšจ. if a backslash character is followed by any other character, the backslash is NOT removed. so watch yourself, okay??

multiple text literals in a row are automatically concatenated, even if they use different quotes ๐Ÿ˜ณ. for example:

FOOBAR="Foo"'bar'

this sets the variable FOOBAR to the text "Foobar" ๐Ÿ’… slay.

๐Ÿ”ฎ Variable substitution ๐Ÿ”ฎ

variable substitution is performed on text literals that's surrounded by double quotation marks โœจ. the "$" character, followed by a variable name, is replaced by that variable's contents.

MAILBOX="$HOME/Mailbox"

this sets the variable MAILBOX to the contents of the variable HOME followed by "/Mailbox" ๐Ÿ“ฌ. variable names must begin with an uppercase letter, a lowercase letter, or an underscore ๐Ÿฆ‹. following that, all letters, digits, and underscores are taken as a variable name, and its contents replace the $ sign and the variable name. it is possible to access variables whose name includes other characters, by using braces, like so:

MAILBOX="${HOME-WORD}/Mailbox"

inserts the contents of the HOME-WORD variable ๐Ÿ’œ. if the variable does not exist, the empty text literal is used to replace the variable name ๐Ÿซฅ. it is not possible to access variables whose names include the } character ๐Ÿšซ.

if the $ character is not followed by a left brace, letter, or an underscore, the $ character remains unmolested in the text literal ๐Ÿ˜Œ. a backslash followed by the $ character results in a $ character in the text literal, without doing any variable substitution ๐Ÿ’….

and heads up โ€” variable substitution is not done in text literals which are surrounded by single quotes (apostrophes) ๐Ÿ™ˆ.

๐ŸŽ Command line arguments ๐ŸŽ

maildrop initializes special variables: $1, $2, and so on, with additional parameters specified on the maildrop command line โœจ. a filter file may use those variables just like any other variables ๐Ÿฆ‹.

๐Ÿ‘‘ Predefined variables ๐Ÿ‘‘

the following variables are automatically defined by maildrop ๐ŸŒธ. the default values for the following variables may be changed by the system administrator ๐Ÿ‘ฉ๐Ÿ’ป. for security reasons, the values of the following variables are always reset to their default values, and are never imported from the environment ๐Ÿ›ก๏ธ:

  • DEFAULT โ€” the default mailbox to deliver the message to ๐Ÿ“ฅ. if the filter file does not indicate a mailbox to deliver this message to, the message is delivered to this mailbox. the default mailbox is defined by the system administrator ๐Ÿ‘‘.
  • FROM โ€” message envelope sender ๐Ÿ’Œ. this is usually the same address as what appears in the From: header, but may not be ๐Ÿ˜ณ. this information may or may not be available to maildrop on your system. the message envelope sender is usually specified with the -f option to maildrop. if the -f option is not given, maildrop looks for the Return-Path: header in the message. as the last resort, FROM defaults to "MAILER-DAEMON" ๐Ÿซ . note that FROM may be empty โ€” the message envelope sender is empty for bounce messages ๐Ÿ“.
  • HOME โ€” home directory of the user running maildrop ๐Ÿ .
  • HOSTNAME โ€” network name of the machine running maildrop ๐Ÿ’ป. obtained from gethostname(3).
  • LOCKEXT โ€” extension for dot-lock files (default: .lock) ๐Ÿ”’.
  • LOCKREFRESH โ€” refresh interval, in seconds, for dot-locks (default: 15) โฐ. when maildrop dot-locks a mailbox, maildrop tries to refresh the lock periodically in order to keep other programs from removing a stale dot-lock. this is only required if a dot-lock exists for a prolonged period of time, which should be discouraged anyway ๐Ÿ™…โ™€๏ธ.
  • LOCKSLEEP โ€” number of seconds to wait to try again to create a dot-lock file, if one already exists (default: 5) ๐Ÿ˜ด.
  • LOCKTIMEOUT โ€” number of seconds to wait before removing a stale dot-lock file (default: 60) ๐Ÿ’€. if a dot-lock file still exists after LOCKTIMEOUT seconds, maildrop assumes that the process holding the lock no longer exists, and the dot-lock file can be safely removed ๐Ÿงน. after removing the dot-lock file, maildrop waits LOCKSLEEP seconds before trying to create its own dot-lock file, in order to avoid a race condition with another process which is also trying to remove the same stale dot-lock, at the same time ๐Ÿ.
  • LOGNAME โ€” name of the user to who the message is being delivered ๐Ÿง‘๐Ÿ’ป.
  • MAILFILTER โ€” this is the name of the original filter file that was given to maildrop on the command line ๐Ÿ“„. this is mostly useful to -default filter files, it allows them to obtain the value of the -M option[3] specified on the command line โœจ.
  • PATH โ€” command execution path ๐Ÿ›ค๏ธ. maildrop resets PATH to the system default (usually /bin:/usr/bin:/usr/local/bin) ๐Ÿ’….
  • SENDMAIL โ€” the mail delivery agent ๐Ÿ“ฎ. when maildrop is instructed to deliver the message to a mailbox whose name begins with the ! character, this is interpreted as a request to forward the message. the SENDMAIL command is executed to forward the message ๐Ÿš€.
  • SHELL โ€” the login shell ๐Ÿš. the shell is used to execute all commands invoked by maildrop.
  • VERBOSE โ€” current Debug level (default: 0) ๐Ÿ”Š. setting VERBOSE to progressively higher values, between 1 and 9, produces debugging output on standard error. maildrop ignores the VERBOSE variable in delivery mode (in order not to confuse the mail transport agent) ๐Ÿคซ.

and that's part one, bestie โ€” keep scrolling, there's more ๐Ÿฆ‹โœจ

๐ŸŒธ UMASK ๐ŸŒธ

The file creation mode mask, in octal (iykyk, octal is the vibe here). Default is 077 and that gives you mailboxes that only the owner can read and write. Slay.

  • 007 โ†’ mailboxes readable/writable by both owner and group
  • 037 โ†’ mailboxes readable by both owner and group, but writable by owner only

Perms on mailboxes that already exist? Not touched, bestie. This setting only affects new mailboxes. And when you're delivering to maildirs, this sets the permissions on new messages only โ€” plus access permissions on messages in maildirs are also affected by the permissions on the maildir directories themselves. Layers, okay? ๐Ÿ’…


โœจ Other special variables โœจ

These following variables are automatically used by maildrop when the filter file is being processed:

๐Ÿ’– EXITCODE

The return code for maildrop. When maildrop successfully delivers a message, it terminates with this exit code, which defaults to 0.

When the to or the cc command is used to deliver the message to an external process, via a pipe, maildrop sets this variable to the exit code of the external process. And since maildrop immediately terminates after completing the to command, that literally means maildrop's exit code will be the exit code of the external process. Periodt.

If the to command does not deliver the message to a process, you must set EXITCODE before the to command โ€” because maildrop terminates immediately after finishing the delivery. No second chances, babes. ๐Ÿ˜ญ

๐Ÿฆ‹ FLAGS

The FLAGS variable is used only when delivering a message to a maildir, and may contain only the following letters: D, F, R, and S. Any order you want, she's flexible. When the message gets delivered to the maildir, the message will be marked with a draft, flag, replied, or seen attribute, correspondingly. Iconic.

FLAGS must be set before the message is delivered to a maildir. And the contents of FLAGS get ignored when delivering on an mbox folder. Not its moment, ig.

๐ŸŒˆ KEYWORDS

The KEYWORDS variable is used only when delivering a message to a maildir, and implements the optional IMAP keyword extension as implemented in the Courier IMAP server. It may optionally be initialized to contain a comma-separated list of keywords. The to, or the cc command, delivers the message to the maildir normally, but also associates the list of keywords in KEYWORDS with the newly delivered message. So extra, so main character. ๐Ÿ‘‘

KEYWORDS must be set before the message is delivered to a maildir. The contents of KEYWORDS are ignored when delivering on an mbox folder.

๐ŸŽ€ LINES

Number of lines in the current message. Heads up though โ€” this may be an approximation. It may or may not take into account the -A option. Use this as criteria for filtering, nothing more. Don't build your whole personality on it, fr fr.

๐Ÿ“ MAILDIRQUOTA

Set this variable in order to manually enforce a maximum size on ANY maildir where the message is delivered. This is an optional feature that must be enabled by the system administrator, see maildirquota(8) for more info. So like, ask your sysadmin first bestie ๐Ÿ’€

๐Ÿงš RETURNCODE

This variable is set when maildrop runs the system command, the xfilter command, or a command that's specified within a pair of backtick characters (command substitution). The RETURNCODE variable will be set to the exit code of the command, after it completes. Very "I'll tell you how it went when it's done" of it.

โญ SIZE

Number of bytes in the message. This may or may not include the -A option. Use this as a criteria for filtering, nothing more. Same energy as LINES, honestly.


๐ŸŒธ Unquoted text ๐ŸŒธ

All text strings in filter files should be in single, or double quotes. However, for convenience sake, quotes can be omitted under certain circumstances. We love a shortcut.

Text that includes ONLY letters, digits, and the following characters: _-.:/${}@ may appear without quotes. Note that this does not allow spaces, or backslashes to be entered โ€” however, the text is still variable-substituted, and the substituted text may contain other characters. Surprise! ๐Ÿ™ˆ

Also, note that patterns (see below) begin with the slash character. Normally, anything that begins with the slash is interpreted as a pattern. However, text immediately after VARIABLE= is interpreted as a string even if it begins with a slash. This is why something like:

MAILDIR=/var/mail

works as expected. Using quotes, though, is highly recommended. And you must use quotes to set a variable to a lone slash, because an unquoted slash is interpreted as a division sign. Math jumpscare ๐Ÿ’€

Long double or singly-quoted text can be broken across multiple lines by ending the line with a lone backslash character, like this:

TEXT="This is a long \
   text string"

The backslash, the newline, and all leading whitespace on the next line is removed, resulting in "This is a long text string". Clean.


๐Ÿ’… Command substitution ๐Ÿ’…

Text enclosed in back-tick characters is interpreted as a shell command. The shell command is executed as a child process by maildrop. Its output is used in place of the command. For example:

DIR=`ls`

places the names of the files in the current directory into the DIR variable. Cute right?

The output of the command will have all newline characters replaced by spaces, and leading and trailing spaces will be stripped (multiple spaces are not removed, though). Also, the contents of the message being delivered is made available to the command on standard input. Sharing is caring. ๐Ÿซถ


โœจ Patterns โœจ

The pattern syntax in maildrop is similar to the grep command's syntax, with some minor differences. A pattern takes the following form in the filter file:

/pattern/:options

pattern specifies the text to look for in the message, in the UTF-8 codeset. pattern must not begin with a space, otherwise the leading slash will then be interpreted as a division sign. If you must search for text that starts with a space, use something like "/[ ] ... /".

The general syntax of maildrop's patterns is described in the pcrepattern(3) manual page, with certain exceptions noted below. maildrop uses the PCRE library to implement pattern matching. Not all features in PCRE are available in maildrop, and the "options" part, which follows the pattern specification, changes the pattern matching further. Consult the pcrepattern(3) manual page for more information, but note the following exceptions:

  • Internal options settings are not supported (but see the "D" maildrop option, below). Do not include option settings in the pattern, doing so will lead to undefined results. Ma'am, that is a warning.
  • Named subpatterns are not implemented. Numbered subpatterns are implemented, see the section called "Pattern Match Results", below.
  • The search pattern gets executed not against the raw message text, but the message transcoded into a canonical UTF-8-based format. This process involves transcoding any non-UTF-8 message content into UTF-8. Additionally, message headers get converted into a canonical format before the search pattern gets executed.

For structured headers with email addresses, the process involves removing extraneous punctuation, or adding missing ones (in situations where a missing punctuation character can be deduced). Additionally certain pre-RFC822 obsolete header formats get converted to canonical form.

This means that header search patterns that include punctuation character may appear not to work against obviously-matching message text. Use reformime -u <message.txt, with message.txt containing the sample message, to see exactly the actual text that gets searched by patterns. That's your vibe check tool, use it. ๐Ÿ”ฅ


๐ŸŒธ Pattern options ๐ŸŒธ

Following /pattern/, there may be an optional colon, followed by one or more options. The following options may be specified in any order:

  • h โ€” Match this pattern in the message's header, and the header of any attachments in the message.
  • H โ€” Match this pattern in the message's main header. Do not match this pattern in the headers of the message's attachments.
  • b โ€” Match this pattern against the message body.
  • D โ€” This is a case sensitive match. Normally the patterns match either uppercase or lowercase text. /john/ will match "John", "john", or "JOHN". Specify the D option for a case-sensitive search: lowercase letters in the pattern must match lowercase letters in the message; ditto for uppercase. She's picky now ๐Ÿ’…

If neither h, H, or b is specified, h is the default, matching the pattern in the message's header, and all attachments' headers. Specifying the b option causes the pattern to be matched against the message body. Specifying b and h causes the pattern to be matched against the entire message.

Normally, each line in the message gets matched against the pattern individually. When applying patterns to a header, multi-line headers (headers split on several lines by beginning each continuation line with whitespace) are silently combined into a single line, before the pattern is applied. Sneaky little glow-up, honestly.


๐Ÿฆ‹ MIME encoding ๐Ÿฆ‹

The pattern must be a valid text string in the UTF-8 codeset, and maildrop should handle messages that use MIME encodings in other known character sets. Options that specify a message header search result in maildrop searching the initial message headers, and any headers of additional MIME sections, in a multipart MIME message. Options that specify a message body search will search through all "text" MIME content.

For a MIME search to succeed, the message must be a well-formed MIME message (with a Mime-Version: 1.0 header). No half-assed MIME, bestie.


โญ Weighted scoring โญ

Patterns are evaluated by maildrop as any other numerical expression. If a pattern is found, maildrop's filter interprets the results of the pattern match as number 1, or true, for filtering purposes. If a pattern is not found the results of the pattern search is zero. Once a pattern is found, the search stops. Second, and subsequent occurrences of the same pattern are NOT searched for.

maildrop can also do weighted scoring. In weighted scoring, multiple occurrences of the same pattern are used to calculate a numerical score. Ooh, math. โœจ

To use a weighted search, specify the pattern as follows:

/pattern/:options,xxx,yyy

where xxx and yyy are two numbers. yyy is optional โ€” it will default to 1, if missing.

The first occurrence of the pattern is evaluated as xxx. The second occurrence of the pattern is evaluated as xxx*yyy, the third as xxx*yyy*yyy, etc... All occurrences of the pattern are added up to calculate the final score. Exponential behavior, she's growing.

Note maildrop does not recognize multiple occurrences of the same pattern in the same line. Multiple occurences of the same pattern in one line count as one occurence. So don't be out here double-dipping on the same line, it doesn't count. ๐Ÿ’€

๐ŸŽ€ Pattern Match Results ๐ŸŽ€

After a pattern is successfully matched, the actual text that is matched is placed in the MATCH variable. For example:

/^From:.*/

okay so that matches a line that looks exactly like this shape:

From: postmaster@localhost

and when it does, the variable MATCH gets set to "From: postmaster@localhost", which you can then use in all the statements that come after. slay. โœจ

If your pattern has subpatterns in it, then the portion of the text that matches the first subpattern lands in the MATCH1 variable. The second subpattern, if there even is one, goes into MATCH2, and so on and so on:

/^From:\s+(.*)@(.*)/

matched against that same line will set MATCH to โ€œFrom: postmaster@localhostโ€, MATCH1 to โ€œpostmasterโ€, and MATCH2 to โ€œlocalhostโ€. and like โ€” obviously in the real world the โ€œFrom:โ€ header is usually much more complicated, and genuinely can't be handled that easily, bestie ๐Ÿ˜ญ. This is just an illustrative example, don't come for me.

๐ŸŒธ Note The contents of all MATCH variables use UTF-8 encoding. As explained in the section called โ€œPatternsโ€, the message gets transcoded into a canonical UTF-8 format, replacing MIME-encoded headers with their UTF-8 equivalent. Consequently, MATCH variables that represent matched text are in UTF-8. For example: if (/^Subject: *(.*)/) This sets MATCH to what's in the โ€œSubjectโ€ header, which could possibly use MIME-encoding, for example: โ€œSubject: =?UTF-8?B?SMOzbGEh?=โ€. The resulting MATCH shows โ€œHรณla!โ€. ๐Ÿฆ‹

โœจ Conversion of maildrop 1.x patterns to 2.0 โœจ

ngl the new PCRE-based pattern matching code in maildrop is completely different from the built-in pattern matching code in maildrop 1.x โ€” but the good news is that very few changes will be required to convert recipes to the new syntax. The only major differences are, like, these two:

  • The subexpression format has changed. Any pattern that uses subexpression needs to be converted. Additionally, references to MATCH2 must be replaced with MATCH1, MATCH3 to MATCH2, and so on. References to plain old MATCH will remain the same. ๐Ÿ“
  • The โ€œwโ€ pattern option is no longer possible, with PCRE. The very few recipes that use this option, if any actually exist, will have to be rewritten in some other fashion. periodt. ๐Ÿ’€

๐Ÿงฎ Expressions ๐Ÿงฎ

The way that maildrop evaluates expressions is, lowkey, numerically, but the results of expressions are stored as text literals. When necessary, text literals are converted to numbers, then the results of a mathematical operation is converted back into a text literal. it's giving shapeshifting, honestly. ๐Ÿฆ‹

๐Ÿ”ข Operators

The following operators carry their usual meaning, and are listed in order from lowest precedence, to the highest:

||
&&
<  <=  >  >=  ==  !=  lt  le  gt  ge  eq  ne
|
&
+  -
*  /
=~ /pattern/
/pattern/  !  ~  function()

๐Ÿ“ Variable assignment

VARIABLE=expression

Assigns the result of the expression to VARIABLE (note โ€” no leading $ in front of variable, bestie, don't do it).

๐Ÿ’œ Note If VARIABLE is NOT surrounded by quotes, then it may contain only letters, numbers, underscores, dashes, and a selected few other characters. In order to initialize a variable whose name contains non-standard punctuation marks, surround the name of the variable with quotes.

๐Ÿ“ฎ cc - deliver a copy of the message

cc expression

The cc statement is very similar to the to statement, except that after delivering the message maildrop continues to process the filter file โ€” unlike the to statement which immediately terminates maildrop once the delivery is complete. Essentially, the message is carbon copied to the given mailbox, and may be delivered again to another mailbox by another cc or to statement. so iconic of it. ๐Ÿ‘‘

See the to statement[8] for more details. When cc is used to deliver a message to a process maildrop will set the EXITCODE variable to the process's exit code.

๐Ÿ”’ dotlock - create a manual dot-lock

dotlock expression {

   ...

}

maildrop automatically creates a lock when a message is delivered to a mailbox. Depending upon your system configuration, maildrop will use either dot-locks, or the flock() system call.

The dotlock statement creates an explicit dot-lock file. Use the flock statement[9] to create an explicit flock() lock.

The expression is a filename that should be used as a lock file. maildrop creates the indicated dot-lock, executes the filtering instructions contained within the { ... } block, and removes the lock. The expression must be the name of the dot-lock file itself, NOT the name of the mailbox file you want to lock. fr fr, don't mix those up. ๐Ÿ™ˆ

๐Ÿ’€ Note With manual locking, it is possible to deadlock multiple maildrop processes (or any other processes that try to claim the same locks). No deadlock detection is possible with dot-locks, and since maildrop automatically refreshes all of its dot-locks regularly, they will never go stale. You'll have maildrop processes hanging in limbo, until their watchdog timers go off, aborting the mail delivery. yes this is as dramatic as it sounds.

๐Ÿ“ข echo - output diagnostic information

echo expression

maildrop will print the given text. This is usually used when maildrop runs in embedded mode, but can be used for debugging purposes. Normally, a newline is printed after the text. If text is terminated with a \c, no newline will be printed. tiny detail, huge difference. โœจ

๐Ÿšจ exception - trap fatal errors

exception {

   ...

}

The exception statement traps errors that would normally cause maildrop to terminate. If a fatal error is encountered anywhere within the block of statements enclosed by the exception clause, execution will resume immediately following the exception clause. it's giving safety net, bestie ๐Ÿซถ

๐Ÿ›‘ exit - terminate filtering unconditionally

exit

The exit statement immediately terminates filtering. maildrop's return code is set to the value of the EXITCODE variable. Normally, maildrop terminates immediately after successfully delivering the message[8] to a mailbox. The exit statement causes maildrop to terminate without delivering the message anywhere. ๐Ÿ˜ญ

The exit statement is usually used when maildrop runs in embedded mode[10], when message delivery instructions are not allowed.

๐Ÿ” flock - create an manual flock() lock

flock expression {

   ...

}

maildrop automatically creates a lock when a message is delivered to a mailbox. Depending upon your system configuration, maildrop will use either dot-locks, or the flock() system call.

The flock statement creates a manual flock() lock. Use the dotlock statement[11] to create a manual dot-lock file.

The expression is the name of the file that should be locked. maildrop creates the lock on the indicated file, executes the filtering instructions contained within the { ... } block, and removes the lock.

โš ๏ธ Note With manual locking, it is possible to deadlock multiple maildrop processes (or any other processes that try to claim the same locks). The operating system will automatically break flock() deadlocks. When that happens, one of the maildrop processes will terminate immediately. Use the exception statement in order to trap this exception condition, and execute an alternative set of filtering instructions. iykyk. ๐Ÿ’…

๐Ÿ” foreach - iterate over text sections matched by a pattern

foreach /pattern/:options
{
   ...
}

foreach (expression) =~ /pattern/:options
{
   ...
}

The foreach statement executes a block of statements for each occurrence of the given pattern in the given message, or expression. On every iteration the MATCH variable will be set to the matched string. Subpatterns set MATCH1, MATCH2, and so on, before beginning each iteration. obsessed with the consistency honestly. ๐ŸŒˆ

All the usual options may be applied to the pattern match, EXCEPT the following:

  • ,xxx,yyy โ€” Weighted scoring is meaningless, in this context. so we're skipping it, periodt.

๐Ÿค” if - conditional execution

if (expression)
{
   ...
}
else
{
   ...
}

Conditional execution. If expression evaluates to a logical true (note โ€” parenthesis are required ๐Ÿ‘€) then the first set of statements is executed. The else keyword, and the subsequent statements, are optional. If present, and the expression evaluates to a logical false, the else part is executed.

maildrop evaluates all expression as text strings. In the context of a logical expression, an empty string, or the number 0 constitutes a logical false value, anything else is a logical true value.

If the if part, or the else part consists of only one statement, the braces may be omitted.

๐ŸŽ€ Note The grammar of this if statement is stricter than usual. If you get baffling syntax errors from maildrop, make sure that the braces, and the if statement, appear on separate lines. Specifically: the closing parenthesis, the closing braces, and the else statement, must be at the end of the line (comments are allowed), and there may not be any blank lines in between (not even ones containing comments only). not even a little aesthetic blank line. tragic but true. ๐Ÿ˜ญ

If the else part contains a single if, and nothing else, this may be combined into an elsif:

if (expression)
{
   ...
}
elsif (expression)
{
   ...
}

The above example is logically identical to:

if (expression)
{
   ...
}
else
{
   if (expression)
   {
      ...
   }
}

Consecutive elsif sequences are allowed:

if (expression)
{
   ...
}
elsif (expression)
{
   ...
}
elsif (expression)
{
   ...
}

Consecutive occurences of elsif commands eliminate a significant amount of indentation, and the resulting code is more readable. main character formatting energy, honestly. ๐Ÿ‘‘

๐Ÿ“ฅ import - access original environment variable

import variable

When maildrop boots up, it's lowkey just vibing and importing the contents of your environment variables, assigning them to internal maildrop variables. Classic. So if you had an environment variable FOO, the internal maildrop variable FOO will have the contents of that environment variable. And from then on? FOO is no different than any other variable, no special treatment, it's just one of the girls. When maildrop runs an external command, the contents of maildrop's variables get exported as the environment for that command. ๐Ÿ’–

Certain variables, like HOME and PATH, are always reset to fixed defaults, for security reasons. (Safety first bestie, no arguments.) Also, in delivery and embedded modes, the environment isn't imported at all โ€” with the exception of system locale environment variables โ€” and maildrop starts with only the fixed default variables. That's it. That's the whole squad. โœจ

The import statement initializes the specified variable with the contents of the original environment variable from when maildrop started. Like, when it first woke up. For example:

echo "PATH is $PATH"
PATH="/bin"
echo "PATH is $PATH"
import PATH
echo "PATH is $PATH"
exit

And that gives you the following output:

PATH is /bin:/usr/bin:/usr/local/bin
PATH is /bin
PATH is /home/root/bin:/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin

So see what happened? When maildrop starts, PATH is set to the fixed default of /bin:/usr/bin:/usr/local/bin. But the original contents of the PATH environment variable was different, and the import statement shows what it was. Character development for your PATH. ๐Ÿ’…


๐ŸŒธ include โ€” execute filtering instructions from another file

include expression

The include statement reads a file, and executes the filtering instructions contained in that file. Important timing tea though: the include statement is processed when the current filter file is being executed. So when maildrop reads the initial filter file, any syntax errors in the filtering instructions are immediately reported, and maildrop will terminate with a return code of EX_TEMPFAIL. But any errors in files specified by include statements are NOT reported, because those files won't be read until the include statement is itself executed. It's giving โœจmysteryโœจ.

If the specified file does not exist, or if there are any syntax errors in the file, maildrop reports the error, and terminates with a return code of EX_TEMPFAIL.


๐ŸŽ€ log, logfile โ€” log message deliveries

logfile expression

log expression

Okay so logging in maildrop is normally turned off, like your read receipts. The logfile statement specifies the file where maildrop will log how the message has been disposed of. The parameter is then name of the file. If the file exists, maildrop appends to the file. (No overwriting, we respect the archives.)

For each delivery โ€” the to[8] and cc[12] statements, and default deliveries โ€” maildrop records the From: and the Subject: fields, together with the current time, in the log file. Receipts, but make it forensic. ๐Ÿ”

The log statement adds additional logging text to the log file. It works exactly like the echo statement, except that the text is written to the logfile, instead of standard output. Same energy, different destination. ๐Ÿ’–


๐Ÿ‘‘ system โ€” execute a system command

system expression

expression specifies an external program that maildrop runs as a subprocess. The subprocess's standard input gets connected to /dev/null (ghosted, respectfully), and the subprocess inherits the standard output and error from maildrop.


๐Ÿฆ‹ to โ€” deliver message to a mailbox

to expression

The to statement delivers the message to a mailbox. expression must evaluate to a valid mailbox. A valid mailbox is either a mailbox file, a maildir, or an external program (which includes forwarding to another address). Three options, all valid, all iconic.

The to statement is the final delivery statement. maildrop delivers the message, then immediately terminates, with its return code set to the EXITCODE variable. If there was an error while delivering the message, maildrop terminates with the EX_TEMPFAIL exit code. A properly-written mail transport agent should re-queue the message, and re-attempt delivery at some later time. So don't panic, it's not gone forever, it's just in its encore era. ๐ŸŒˆ

An expression that begins with the "|" character specifies an external program to run to handle the actual delivery. The SHELL variable specifies the shell to execute the given command. The message is provided to the command on standard input. maildrop's exit code will be the process's exit code.

An expression that begins with an exclamation mark, "!", specifies a whitespace-delimited list of E-mail addresses to forward the message to. The program specified by the SENDMAIL variable is run as an external program, with the list of E-mail addresses provided as parameters to the program.

Otherwise, expression names the mailbox where maildrop delivers the message. If expression is a directory, maildrop assumes that the directory is a maildir directory. Otherwise, maildrop will deliver the message to a file, formatted in traditional mailbox format. maildrop will use either dot-locking, or flock()-locking when delivering the message to the file. Locked in, literally and technically. ๐Ÿ”’


๐Ÿ’œ while โ€” repeatedly execute a block of statements

while (expression)
{
   ...
}

The expression is repeatedly evaluated. Each time it evaluates to a logical true[13], the statements inside the braces are executed. When expression evaluates to a logical false, the while loop is over. Take care to avoid infinite loops. Seriously bestie, watch out โ€” that's the one way to get stuck in a loop and in the group chat. ๐Ÿ˜ญ


๐Ÿงš xfilter โ€” filter message through another program

xfilter expression

expression specifies an external program that maildrop runs to filter the current message. The current message will be piped to the filter program as standard input. The output of the filter program replaces the current message being delivered. The external program must terminate with an exit code of 0. If the external program does not terminate with an exit code of 0, or if it does not read the message from the standard input, maildrop terminates with an exit code of EX_TEMPFAIL. No half-finished glow-ups allowed. ๐Ÿ’…


โญ || โ€” logical or

expression1 || expression2

If expression1 evaluates to a logical true, the result of the || is expression1, otherwise it's expression2, which is evaluated.

maildrop uses the following concept of true/false: an empty text literal, or a text literal that consists of the single character "0" is a logical false value. Anything else is a logical true value. That's the whole binary, periodt.


โญ && โ€” logical and

expression1 && expression2

If expression1 evaluates to a logical false, the result of the && is expression1, otherwise it's expression2, which is evaluated.

maildrop uses the following concept of true/false: an empty text literal, or a text literal that consists of the single character "0" is a logical false value. Anything else is a logical true value.


๐Ÿ”ฅ <, <=, >, >=, ==, != โ€” numerical comparison

expression1 < expression2

expression1 <= expression2

expression1 > expression2

expression1 >= expression2

expression1 == expression2

expression1 != expression2

These operators compare their left hand side expression against their right hand side. These operators compare the numerical values of each side, as floating point numbers. If the numbers compare as indicated, the result of the comparison is the text string "1", otherwise it is the text string 0.

Note ๐Ÿ’€ Comparisons are not associative: "a < b < c" is an error. If it is absolutely necessary, use "(a < b) < c".

๐ŸŒธ lt, le, gt, ge, eq, ne โ€” text comparison

expression1 lt expression2

expression1 le expression2

expression1 gt expression2

expression1 ge expression2

expression1 eq expression2

expression1 ne expression2

These operators compare their left hand side expression against their right hand side. These operators compare each side as text strings (alphabetically, although the text may include anything). If the text strings compare as indicated, the result of the comparison is the text string "1", otherwise it is the text string 0.

Note ๐Ÿซ  Comparisons are not associative: "a lt b lt c" is an error. If it is absolutely necessary, use "(a lt b) lt c". (But why would you?).

๐Ÿ’œ | โ€” bitwise or

expression1 | expression2

This is the bitwise or operator. Its result is a 32 bit integer, which is a bitwise-or combination of the left hand side and the right hand side.


๐Ÿ’œ & โ€” bitwise and

expression1 & expression2

This is the bitwise and operator. Its result is a 32 bit integer, which is a bitwise-and combination of the left hand side and the right hand side.


โœจ +, -, *, / โ€” numerical operations

expression1 + expression2

expression1 - expression2

expression1 * expression2

expression1 / expression2

These are numerical, floating point, operators. Math is math, bestie. โž—


๐ŸŽ€ =~ /pattern/:options โ€” pattern match against string

expression =~ /pattern/:option

The left hand side of the =~ operator can be any expression. The right hand side is always a pattern specification. The result of the operator is the weighted match of the pattern against expression (if the options do not specify weighted scoring, the result is simply 1 if the pattern was found, 0 if not).

See "Patterns[14]" for more information.


๐Ÿฆ‹ /pattern/:options โ€” pattern match against message

/pattern/:option

The result of this operator is the weighted match of the pattern against the current message (if the options do not specify weighted scoring, the result is simply 1 if the pattern was found, 0 if not).

See "Patterns[14]" for more information.


๐ŸŒˆ !, ~ โ€” logical/bitwise not operator.

! expression

~ expression

The result of the ! operator is a logical opposite of its right hand side expression. If the right hand side expression evaluated to a logical true, the result is a logical false. If it evaluated to a logical false, the result is a logical true. Flip the script, spiritually. ๐Ÿ™ƒ

maildrop uses the following concept of true/false: an empty text literal, or a text literal that consists of the single character "0" is a logical false value. Anything else is a logical true value.

The way the ~ operator hits is: it hands you the bitwise complement of whatever expression is sitting on its right. That right-hand expression gets evaluated as a 32 bit integer, and the result of this operator is the bitwise complement of that result. ๐Ÿ’…


escape(string) โ€” escape special characters in a string

escape(expression)

escape gives you back its one and only argument, but with a backslash slapped in front of every single special character it finds. โœจ And "special character" here means literally any of these:

|!$()[]\+*?.&;`'-~<>^{}"

This is the move for when you're matching pattern sections[15] and then you wanna take one of those sections and match it again. Like so:

if ( /^From:\s*(.*)/ )
{
   MATCH1=escape($MATCH1)
   if ( /^Subject:.*$MATCH1/ )
   {
      ...
   }
}

Okay so this snippet checks whether the stuff in the From: header also shows up in the Subject: header. If you didn't use escape, then any special characters in the From: header that also do something in regular expressions โ€” think * or + โ€” would bring unpredictable behavior, and most likely a syntax error. ๐Ÿ’€

Why does this special character list also include characters that maildrop's regular expressions don't even use? So that maildrop's variables can be used on the command line of a shell command run by the xfilter command, backtick characters, or to or cc commands. ๐ŸŒธ

And yeah, using data from an external data source is dangerous, and it may result in inadvertent exploits, but using the escape function should hopefully result in fewer surprises. ๐Ÿซถ


gdbmopen, gdbmclose, gdbmfetch, gdbmstore โ€” GDBM support in maildrop

These functions give you support for GDBM database files. Peep maildropgdbm(5)[16] for more info.

Note ๐Ÿซง Your system administrator can disable GDBM support in maildrop, so these commands may not be available to you. ๐Ÿ˜ญ

getaddr(string) โ€” extract RFC 2822 addresses from a header

if ( /^From:\s*(.*)/ )
{
   ADDR=getaddr($MATCH1)
}

Usually you point this at a header that contains RFC 2822[17] addresses. It pulls the actual addresses out of the header โ€” no comments, no extraneous punctuation, none of that. Each address is followed by a newline character. So if string contains:

joe@domain.com (Joe Brown), "Alex Smith" <alex@domain.com>, tom@domain.com

Then the result of the getaddr function is this string right here:

joe@domain.com<NL>alex@domain.com<NL>tom@domain.com<NL>
Note ๐Ÿ’– Because getaddr() interprets RFC 2822[18] loosely, you don't have to strip off the "To:" or the "Cc:" header from the string before feeding it to getaddr(). For real, check this: the snippet below takes all the addresses in the message and concatenates them into a single string, separated by spaces:
ADDRLIST=""
foreach /^(To|Cc): .*/
{
    foreach (getaddr $MATCH) =~ /.+/
    {
        ADDRLIST="$ADDRLIST $MATCH"
    }
}
Note ๐Ÿ™ˆ In certain rare situations, RFC 2822[18] allows spaces to be included in E-mail addresses, so this example is just educational.

hasaddr(string) โ€” Search for an address

if ( hasaddr(string) )
{
   ...
}

string is of the form user@domain. The hasaddr function returns 1 if this address is included in any To:, Cc:, Resent-To:, or Resent-Cc: header in the message, otherwise it returns 0.

And no, this is not just a simple text search, bestie. Each header is parsed according to RFC822. Addresses found in the header are extracted, ignoring all comments and names. The remaining addresses get checked, and if string is one of them, hasaddr returns 1, otherwise it returns 0.

The comparison is case-insensitive. Which actually violates RFC822 (and several others) a little bit, because the user part of the address may be (but is not required to be) case sensitive. ๐Ÿ’…


length (string) โ€” length of a string

if (length(string) > 80)
{
   ...
}

The length function returns the number of characters in string.


lookup (expr, 'filename', 'options') โ€” read file for patterns

if (lookup(expr, file, "option"))
{
   ...
}

expr is any expression. filename is a name of a file containing a list of patterns. Quick heads up: filename is relative to the current directory, which is the home directory of the user when maildrop runs in delivery mode, or embedded mode. Then maildrop reads the file. Blank lines get ignored, and so does anything that begins with the # character (those are comments). โœจ

Leading whitespace gets removed (but not trailing whitespace โ€” take care!), and the remaining contents of each line are interpreted as a pattern which is matched against expr. As soon as the match is found, lookup returns "1". If no match is found after reading the entire file, lookup returns "0". For example:

if ( /^To:\s*(.*)/ && lookup( $MATCH1, "badto.dat" ))
{
    exit
}

The file badto.dat contains these two lines:

friend@public
^[^@]*$

So if a message has a To: header containing the text "friend@public", or it doesn't contain at least one @ character, then the message will be silently dropped on the floor (maildrop will terminate without delivering the message anywhere). ๐Ÿ’€

options are the pattern matching options to use. The only supported option is "D" (the rest are meaningless, in this case).

Note ๐Ÿ˜ญ Be careful with discarding messages like that, fr fr. Pattern matching can be tricky, and a slight miscalculation can cause mail to be unintentionally discarded. It is much desirable to first deliver message to a separate folder or mailbox, and once the filter is verified to work correctly, change it so the messages are discarded completely.

substr(string,start [,count]) โ€” return substring

foo=substr($foo, 1, 10)

The substr function extracts characters from string beginning with character #start. If count is specified, at most count characters starting at position start are kept, any excess is trimmed. โœ‚๏ธ


time โ€” return current time

foo=time

The time function returns the current time, in seconds, since January 1, 1970. This function is useful when using GDBM files. Check maildropex(7)[19] for an example of using the time function. โฐ


tolower(string) โ€” Convert string to lowercase

foo=tolower(string)

This function returns the string with all uppercase characters replaced by lowercase characters.


toupper(string) โ€” Convert string to uppercase

foo=toupper(string)

This function returns the string with all lowercase characters replaced by uppercase characters. ๐Ÿ” 


๐Ÿ“œ Statements ๐Ÿ“œ

The filter file is read by maildrop ($HOME/.mailfilter or another file), and it contains filtering statements, one per line. The filtering language used by maildrop has a loosely-defined grammatical structure. She's giving "vibes over strict grammar" and honestly? iconic.

Statements are listed one per line. Multiple statements may be listed on the same line by separating them with semicolons. To continue a long statement on the next line, terminate the line with a backslash character.


๐Ÿ’€ BUGS ๐Ÿ’€

If getaddr() or hasaddr() functions are used on broken headers, the results are unpredictable. No cap, nobody knows what happens. ๐Ÿ˜ญ

hasaddr() is completely case insensitive. This actually violates a few RFCs, because the userid portion of the address could be case-sensitive, but it's not in too many cases, so there. ๐Ÿ’…


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€


๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Sam Varshavchik

  • Author

๐ŸŽ€ NOTES ๐ŸŽ€

  1. Courier mail server https://www.courier-mta.org/
  2. dot-courier(5) http://www.courier-mta.org/maildrop/dot-courier.html
  3. value of the -M option http://www.courier-mta.org/maildrop/maildrop.html#moption
  4. maildirquota(8) http://www.courier-mta.org/maildrop/maildirquota.html
  5. system http://www.courier-mta.org/maildrop/#system
  6. xfilter http://www.courier-mta.org/maildrop/#xfilter
  7. PCRE http://www.pcre.org
  8. See the to statement http://www.courier-mta.org/maildrop/#to
  9. flock statement http://www.courier-mta.org/maildrop/#flock
  10. embedded mode http://www.courier-mta.org/maildrop/maildrop.html#embedded
  11. dotlock statement http://www.courier-mta.org/maildrop/#dotlock
  12. cc http://www.courier-mta.org/maildrop/#cc
  13. evaluates to a logical true http://www.courier-mta.org/maildrop/#if
  14. Patterns http://www.courier-mta.org/maildrop/#patterns
  15. matching pattern sections http://www.courier-mta.org/maildrop/#patmatch
  16. maildropgdbm(5) http://www.courier-mta.org/maildrop/maildropgdbm.html
  17. RFC 2822 http://www.rfc-editor.org/rfc/rfc2822.txt
  18. RFC 2822 http://www.rfc-editor.org/rfc/rfc822.txt
  19. maildropex(7) http://www.courier-mta.org/maildrop/maildropex.html
  20. lockmail(1) http://www.courier-mta.org/maildrop/lockmail.html
  21. maildrop(1) http://www.courier-mta.org/maildrop/maildrop.html
  22. reformail(1) http://www.courier-mta.org/maildrop/reformail.html

And that's the whole page, bestie, the entire MAILDROPFILTER(7) served fresh from the Courier Mail Server crew, dated 01/10/2026. Go write a filter that actually works. ๐Ÿ’–โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from
  • ubuntu stonking: maildrop 4.0.4-1 ยท GPL-3~with-OpenSSL-exception
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms