MAILMUN...TIPLEXOR(8)

πŸ“¦ version 3.20, in alpine 3.24

πŸŽ€ MAILMUNGE-MULTIPLEXOR πŸ’… (section 8 of the manual, bestie)

okay so this is the page for mailmunge-multiplexor β€” it lives in section 8 of the manual, aka the "system administration commands" girlies. she's a process pool controller for mail filters and she is so important, like genuinely the backbone of the whole operation. let's get into it πŸ’–


✨ NAME ✨

mailmunge-multiplexor β€” the process pool controller for mail filters. that's it, that's the whole name. she controls the pool. periodt.


🌸 SYNOPSIS 🌸

mailmunge-multiplexor [options]

that's it! just run her with some options, she handles the rest, no cap.


πŸ¦‹ DESCRIPTION πŸ¦‹

mailmunge-multiplexor manages a pool of Perl processes whose whole job is scanning e-mail. she's designed to work side-by-side with mailmunge and your Perl filter program, like a little duo, iykyk.

here's the vibe: mailmunge-multiplexor opens a UNIX-domain socket and just sits there listening for work requests from mailmunge. as requests come in, she creates Perl processes as needed to scan the mail. and the iconic part? the Perl processes are NOT killed when scanning is done β€” they keep running in a loop. they get re-used for the next e-mail messages. this eliminates the huge overhead of starting a brand-new Perl process for every single incoming message. we love efficiency, we stan efficiency πŸ’…

and to avoid memory leaks (the eternal enemy), the Perl processes get killed off after they've handled some number of scans. self-care, honestly 🫢


🧚 OPTIONS 🧚

okay there are a LOT of these, so buckle up bestie, we're going through every single one:

  • -U user β€” runs the multiplexor as user instead of root. this option is mandatory, and it has to match the -U option you supplied to mailmunge. no matching, no vibes.
  • -m minWorkers β€” the minimum number of Perl processes to keep running at all times. default is zero.
  • -x maxWorkers β€” the max number of Perl processes running simultaneously. if a request comes in and every single process is busy, a temporary failure gets signalled to the SMTP peer. default is 2.
  • -r maxRequests β€” the max number of requests a given process handles before it gets killed and a replacement started. default is 500.
  • -i idleTime β€” the idle time in seconds after which to kill off excess Perl processes. like, if the process is idle for longer than this time AND there are more than minWorkers running, the process gets killed. heads up though: this is implemented as a timer that ticks every idleTime seconds, so processes may actually sit idle for up to twice this time before they get killed. the default for idleTime is 300 seconds.
  • -V maxLifetime β€” the max lifetime in seconds of a worker before it's killed and a replacement started. default is -1, which means no max lifetime at all, she can live forever if she wants. πŸ“Œ but note: the lifetime check only happens when a worker becomes idle after a request, or every time the idle-timeout check is made. so on a very quiet system, workers may live longer than maxLifetime by as much as idleTime. also note the lifetime is measured not from when the worker started running, but from when it was assigned its very first request. a completely-idle worker that has never processed any requests will NOT be terminated by the maxLifetime setting. she's just chillin', leave her alone.
  • -b busyTime β€” the longest a Perl process is allowed to spend scanning an e-mail before it's declared hung up and killed. default is 120 seconds. clock's ticking bestie ⏰
  • -Z β€” this option says the multiplexor should accept and process "status updates" from busy workers. note that this consumes one extra file descriptor per worker, plus a small amount of CPU time per status update. it's giving "small price to pay" energy.
  • -c cmdTime β€” the timeout for communication between mailmunge-multiplexor and mailmunge, or between mailmunge-multiplexor and a Perl scanning process. default is 10 seconds. this timeout should be kept quite short, fr fr.
  • -w waitTime β€” when mailmunge-multiplexor starts the initial workers, or needs to bring the number of running workers up to the number defined by the -m option, she does not start all the workers at once, because that could overload your server (and we don't do that here). instead, she starts one worker every waitTime seconds. default value for waitTime is 1.
  • -W waitTime β€” if you use this option, mailmunge-multiplexor will never activate a worker until waitTime seconds have elapsed since the last worker activation. this could result in mail being tempfailed if worker activations don't keep pace with incoming mail. however, it may be preferable to tempfail mail rather than allow the load on your server to spike up too quickly. default value for this option is 0, meaning she'll start workers as quickly as necessary to keep up with incoming mail. > except in very unusual situations, you should not need to use this option and we do not recommend that it be used. so like... don't, bestie πŸ™ˆ
  • -z spooldir β€” set the spool directory to spooldir. if you omit this option, the spool directory defaults to /var/spool/mailmunge.
  • -s pathName β€” the UNIX-domain socket on which mailmunge-multiplexor listens for requests. this should be specified as an absolute pathname. if this option isn't supplied, it defaults to mailmunge-multiplexor.sock under the spool directory.
  • -a socket β€” a socket for listening for requests. this is similar to the -s socket, except that a restricted set of requests get processed on it. on this socket, the multiplexor will only process requests asking for status β€” she will NOT accept any commands to do scanning or anything that would consume a worker. see the "SOCKET SPECIFICATION" section for the format of socket. read-only mode, essentially. she's just there to answer questions πŸ’…
  • -p fileName β€” causes mailmunge-multiplexor to write its process-ID (after becoming a daemon) to the specified file. the file will be owned by root. πŸ‘‘
  • -o fileName β€” causes mailmunge-multiplexor to use fileName as a lock file to avoid multiple instances from running. if you supply -p but not -o, then mailmunge-multiplexor constructs a lock file by appending .lock to the pid file. however, this is less secure than having a root-owned pid file in a root-owned directory and a lock file writable by the user named by the -U option. (the lock file must be writable by the -U user.) security girlies, take notes πŸ“
  • -f filter_path β€” normally, mailmunge-multiplexor executes a Perl filter script called /etc/mailmunge/mailmunge-filter.pl to scan the e-mail. however, you can have her execute any program you like by specifying the full path to the program with the -f option. this program must obey the protocol documented in mailmunge-protocol β€” see that manual page for details. them's the rules.
  • -l β€” log certain events, including the output of the Perl workers' standard-error, using syslog. normally, the multiplexor does not log much information at all. so if you want the tea β˜•, use this.
  • -d β€” write debugging information about event-handling code in /var/log/mailmunge-event-debug.log. this is only of use to people debugging mailmunge-multiplexor β€” like, if that's not you, skip it, no stress.
  • -R kbytes β€” limits the resident-set size of the worker filter processes to kbytes kilobytes. ⚠️ this limit is not supported on all operating systems; it's known to work on Linux. so, Linux girlies only for this one.
  • -M kbytes β€” limits the total memory space of worker filter processes to kbytes kilobytes. this limit is supported on all operating systems that support the setrlimit system call, which should include most modern UNIX systems. πŸ™Œ
we recommend that you monitor your worker filter processes and get a feel for how much memory they use. you should then limit the memory to two or three times the worst-case that you've observed. this can help mitigate denial-of-service attacks that use complicated MIME messages to force your Perl filter to consume lots of memory. like, protect your girl, okay? πŸ›‘οΈ
  • -h β€” print usage information and exit. the classic help flag, we love her.
  • -t filename β€” log statistical information to filename. see the section "STATISTICS" for more info.
  • -T β€” log statistical information using syslog. you may use -t and -T together, in which case statistical information gets logged in a file AND using syslog. why not both, honestly πŸ’β€β™€οΈ
  • -u β€” flush the statistics file after every write. normally, mailmunge-multiplexor does not flush the file β€” this is the best choice for minimizing disk I/O on a busy mail server. however, if you wish to watch statistics entries in real-time, you should enable flushing. trade-offs, bestie.
  • -D β€” do not fork into the background and become a daemon. instead, stay in the foreground. useful mainly for debugging, or if you have a supervisory process such as systemd managing mailmunge-multiplexor. let systemd be the main character for once.
  • -q queue_size β€” normally, if all workers are busy and mailmunge-multiplexor receives another request, she fails it with the error No free workers. however, if you use the -q option, then up to queue_size requests get queued. as soon as a worker becomes free, the queued requests are handed off in FIFO order. if the queue is full and another request comes in, then the request gets failed with No free workers. the queue line is real 🫢
  • -Q queue_timeout β€” queued requests should not stay on the queue indefinitely, that's just rude. if a queued request can't be processed within queue_timeout (default 30) seconds of being placed on the queue, it's failed with a Queued request timed out message. see the section "QUEUEING REQUESTS" for more discussion. patience has limits, periodt.
  • -O sock β€” listen on a notification socket for connections from listeners. mailmunge-multiplexor can inform external programs of state changes by sending messages over a notification socket. the external programs connect to this socket and then just listen for notifications. πŸ‘‚ see the section "SOCKET SPECIFICATION" for the format of sock.
see the mailmunge-notify man page for details of the notification protocol. go read it, it's good for you πŸ’œ
  • -N map_sock β€” listen on a map socket for Sendmail SOCKETMAP connections. as of Sendmail 8.13, you can define a Sendmail map type that talks to a daemon over a socket. mailmunge-multiplexor implements that protocol; see filter_map in Mailmunge::Filter for details.
see the section "SOCKET SPECIFICATION" for the format of map_sock.
  • -I backlog β€” when mailmunge-multiplexor creates a listening socket, she calculates the backlog argument to listen based on the maximum number of workers. however, you can explicitly set this backlog with the -I option. setting the backlog to a high value (around 30-50) may help on a very busy server. 🚦 if you see mail log messages saying MXCommand: socket: Connection refused during busy periods, then that's your sign that you need a higher listen backlog. she's telling you bestie, listen to her.
  • -L interval β€” log the worker status every interval seconds. this logs a line using syslog; the line looks like this:
Worker status: Stopped=s Idle=i Busy=b Killed=k Queued=q Msgs=m Activations=a

🌸 okay that's everything up to here β€” more of this page is coming in part 2, so don't go anywhere bestie 🫢

Here, Stopped is the number of non-running workers, Idle is the number of idle workers, Busy is the number of busy workers, Killed is the number of killed workers that haven't been reaped yet, Queued is the number of queued requests, Msgs is the total number of messages processed since the multiplexor started running, and Activations is the number of times a Perl process has been started since the multiplexor started running. Bestie, that's your whole dashboard right there 🩷

If you pass an interval of 0 (which is literally the default), then no periodic status logging happens at all. And if you pass an interval of less than 5 seconds, it gets silently reset to 5 seconds β€” no warning, no drama, it just does it πŸ™ˆ

  • -S facility β€” sets the syslog facility for log messages. Default is mail. Peep openlog for the list of valid facilities β€” and you can totally use either the short name (mail) or the long name (LOG_MAIL) for the facility name, we're flexible like that πŸ’…
  • -E β€” tells the multiplexor to create an embedded Perl interpreter, which can improve performance dramatically. But bestie, read the section EMBEDDING PERL first, okay? πŸ‘€
  • -X n β€” says the multiplexor should fire off a "tick" request every n seconds. That makes your filter's tick method get called (if it's defined, that is). Heads up though: you have zero control over which worker runs the tick. And if all the workers are busy when a tick comes due, that tick request is straight-up skipped and a warning gets logged 😭
  • -P n β€” makes the multiplexor run n tick requests in parallel. Each tick runs as often as you said with the -X argument. (Skip the -P option and it behaves like you'd passed -P 1, no cap.)

So when you run parallel ticks, each tick gets an integer identifying its "type". The type ranges from 0 to n-1. And even though there can be up to n tick requests running at once, only one tick of each type is active at any given moment. iykyk 🧚

  • -Y label β€” sets the tag used in the multiplexor's syslog messages to label instead of mailmunge-multiplexor. Cute branding, basically πŸŽ€
  • -G β€” normally mailmunge-multiplexor uses a umask of 027 when it creates listening sockets. If you want the sockets readable and writeable by the group and not just the owner, pass -G. That makes the umask 007 whenever UNIX-domain sockets get created. Group chat access, unlocked 🫢
  • -y n β€” caps the maximum number of concurrent recipok checks at n, on a per-domain basis. The value of n can range from 0 (in which case, no limit at all) up to maxWorkers, where maxWorkers is whatever you passed to the -x option. If n is outside that range, it's ignored (and again, no limit gets applied).

So, recipok ultimately invokes the filter_recipient function in your filter. And if you're doing recipient verification against servers that might be slow or unreliable, you can use -y to limit how many concurrent recipient verifications happen per domain. That way, if one domain's server gets all sluggish, it won't gobble up every available worker for recipient verification. Instead its RCPT commands get tempfailed, and there are still workers free to handle RCPT commands for other domains. Teamwork, baby 🌈


🌸 SOCKET SPECIFICATION 🌸

The -a, -N and -O options all take a socket as an argument, and that socket can be specified as one of these:

  • /path/to/socket β€” a UNIX-domain socket.
  • inet:portnum β€” a TCP socket bound to port portnum, but it only accepts connections from the IPv4 loopback address (127.0.0.1).
  • inet_any:portnum β€” a TCP socket bound to port portnum that will accept connections from any address. Use inet_any with caution, bestie, that's a whole open door πŸšͺ😬
  • inet6:portnum β€” a TCP socket bound to port portnum listening on the IPv6 loopback address.
  • inet6_any:portnum β€” a TCP socket bound to port portnum listening on the IPv6 wildcard address.

πŸ¦‹ QUEUEING REQUESTS πŸ¦‹

Normally, if all the workers are busy, any extra requests just fail immediately. But the -q and -Q options let you queue requests for a short amount of time. This whole facility is meant to gracefully handle a temporary overload β€” and honestly, most of the time your queue should be empty. Like, empty empty πŸ“

Because mailmunge checks the number of free workers when a connection opens and fails the connection if there are none free, the point of the queue is to let SMTP transactions that are already underway keep going if there's a slight overload. Any new connections still fail when all workers are busy, but existing connections are allowed to continue. Queuing requests can improve throughput on extremely busy servers. slay πŸ’…

Quick note: if you pass the -q option to mailmunge, then even new connections are allowed to queue. That can improve throughput by keeping your worker utilization higher.

And the -R option to mailmunge can reserve a specified number of workers for connections coming from the loopback address. Using -R has the side-effect of letting new connections from the loopback address queue. Fun little bonus ✨


πŸ’œ EMBEDDING PERL πŸ’œ

Normally, when mailmunge-multiplexor activates a worker, it forks and execs your Perl filter program. But if the multiplexor was compiled with embedded Perl support, and you pass the -E command-line option, then it works like this:

  1. It creates an embedded Perl interpreter, and sources your Perl filter program with a special command-line argument telling it to read the filter, but not to enter the main loop.
  2. Each time a worker is activated, the multiplexor calls fork() and calls the Perl function _mailmunge_do_main_loop. That invokes filter_initialize and then runs your filter's main loop.

On some very old platforms (for example, Red Hat Linux 7.3 with Perl 5.6.1), it is not safe to destroy and recreate a Perl interpreter without causing a memory leak. On those platforms, if you try to reread the filter file (by sending the multiplexor a HUP signal or a reread command), the filter will not be re-read, and a message gets logged to syslog. On those platforms you have to kill and restart mailmunge-multiplexor if you change the filter file. Annoying, I know 😭

On most platforms, though, a filter reread happens by destroying and re-creating the embedded interpreter, re-sourcing your Perl filter, and killing workers as soon as they're idle. Clean. Iconic. No notes πŸ‘‘


πŸ“Š STATISTICS πŸ“Š

With the -t option, mailmunge-multiplexor logs certain events to a file. You can post-process that file to gather statistics about the multiplexor β€” use it to tune how many workers you run, adjust timeouts, and so on. Data girlies stay winning πŸ’…

Each line of the file looks like this:

"YYYY/MM/DD:HH:MM:SS timestamp event key=val key=val..."

Here, YYYY/MM/DD:HH:MM:SS is the local time of day. Timestamp is the number of seconds since January 1, 1970. Event is the name of an event. The valid events are:

  • StartWorker β€” a worker process has been started.
  • KillWorker β€” a worker process has been killed.
  • ReapWorker β€” a dead worker process has been reaped. It's possible to get a ReapWorker event without a previous KillWorker event if the worker process terminated abnormally. Rude of it, honestly πŸ’€
  • StartFilter β€” a worker process has begun filtering an e-mail message.
  • EndFilter β€” a worker process has finished filtering an e-mail message.

The possible keys in the key=value pairs are:

  • worker=n β€” the worker involved in the event. Every worker is identified by a small integer.
  • nworkers=n β€” the total number of running workers immediately after the event happened.
  • nbusy=n β€” the number of busy workers (the ones processing an e-mail message) immediately after the event happened.
  • reason="string" β€” the reason for a StartWorker or KillWorker event. (Only present for those events.)
  • numRequests=n β€” the number of e-mails processed by the worker. Only present for an EndFilter event.

If you send the mailmunge-multiplexor process a SIGHUP signal (kill -1 pid), it closes and reopens the statistics file. Super useful during log file rotation, ngl πŸ”„

If you send the mailmunge-multiplexor process a SIGINT signal (kill -INT pid), it terminates all active-but-idle workers. Also, any active-and-busy workers terminate as soon as they finish filtering the current message. Handy for forcing a reread of the filter rules file without stopping and restarting Sendmail. Big brain move 🧠✨

If you send the mailmunge-multiplexor process a SIGTERM signal (kill pid), it terminates all workers and exits immediately. That's the "we're so done here" button 🫑


πŸ‘€ SEE ALSO πŸ‘€

mailmunge, Mailmunge, Mailmunge::Filter, mailmunge-protocol, mailmunge-notify, mm-mx-ctrl


🌟 AUTHOR 🌟

mailmunge-multiplexor was written by Dianne Skoll <dianne@skollsoft.com>. Queen behaviour, fr fr πŸ‘‘


πŸ’– LICENSE πŸ’–

This code is licensed under the terms of the GNU General Public License, version 2.


And that's a wrap, bestie! πŸŽ€ You just read the perl v5.42.2 edition, dated 2025-10-05 β€” go forth and multiplex responsibly πŸŒˆπŸ’œ

πŸ“¦ where this page comes from