launchd, launchctl, postfix and postmulti on MacOS 11+
or
Enable postfix to receive mail from remote hosts on MacOS
As noted in part 1, MacOS uses postfix as the default MTA. The postfix instance receives mail from local daemons. In addition, no postfix configuration changes are necessary to send mail from the default MacOS instance to local or remote machines with a listening smtpd on port 25. This is outlined in Part 1.
Thus, the Apple default postfix instance is similar to a postfix null client and a basic outgoing mail client. Here is the last stanza of /etc/postfix/main.cf on MacOS 12.3.1:
# Apple Defaults
#
message_size_limit = 10485760
mailbox_size_limit = 0
biff = no
mynetworks = 127.0.0.0/8, [::1]/128
smtpd_client_restrictions = permit_mynetworks permit_sasl_authenticated permit
recipient_delimiter = +
tls_random_source = dev:/dev/urandom
smtpd_tls_ciphers = medium
inet_interfaces = loopback-only
The default postfix instance is listening only on localhost for mail sent from cron, other system daemons, and by the 'mail' command in Terminal. (Although, in the Apple implementation, the process is not on and continually listening, it is triggered by launchd when a message is sent to /var/spool/postfix/maildrop.)
To get a fully functional MTA, we can just leave that default postfix instance alone and add on another postfix instance which recieves mail from remote hosts. This is much simpler than the rigaramole we had to go through in Part 2 - although that's a nice introductory exercise to launchd and launchctl. For this, we will use postmulti, a standard compnent of postfix installs which is installed by default on MacOS. The README linked above is a pretty clear guide to how to do this. This should be read carefully.
**
Sections below are functional but the documentation for the public is not totally complete.
**
1. No changes to the default postfix LaunchDaemon: Leave it running to handle system mail and to send mail.
Further, according to the postfix documentation for postmulti, at least one instance of the default instance must be running.
2. Enable multiple postfix instances with postmulti:
See http://www.postfix.org/MULTI_INSTANCE_README.html#init for a step by step description. but, basically:
# postmulti -e init
gets you there.
3. Configure the new postfix instance ("postfix-in"):
In the README, instructions for a null-client and an output instance are listed, but the default Mac postfix configuration already includes those in the default instance so we don't need to bother with that.
Create the input instance postfix-in:
# postmulti -I postfix-in -G mta -e create
Now, configure the postfix input instance:
See the write-up in http://www.postfix.org/MULTI_INSTANCE_README.html regarding the "input instance." This pretty clearly will get you up and running.
4. Start the postfix.in instance with postmulti with appropriate command line options:
# postmulti -i postfix-in -x postconf -e "master_service_disable =" "authorized_submit_users = root"
# postmulti -i postfix-in -e enable
# postmulti -i postfix-in -p start
After the postfix.in instance is running, you can stop and start all postfix instances with:
postfix stop
postfix start
However, we'll let the default instance take care of itself. You can deal with your new input instance like so:
# postmulti -i postfix-in -p start
# postmulti -i - -p stop
# postmulti -i postfix-in -p status
# postmulti -i postfix-in -p flush
# postmulti -i postfix-in -p reload
The use of these commands should be self-apparent.
5. Test mail sending and receiving from local and remote sources:
6. Configure launchd to enable and start postfix-in instance at boot:
We'll now create a LaunchDaemon which will start postfix-in on start
The command 'postmulti -i postfix-in -p start' will be used in the LaunchAgent plist. This will start the input instance on login by the user(s) - and automatically stop it on log out by using SIGTERM. This is not the postfix way but it appears that MacOS treats the default postfix installation and instances that way without problem, so we will depend on launchd.
a. Create the postfix-in plist:<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.postfix.in</string>
<key>Program</key>
<string>/usr/libexec/postfix/master</string>
<key>ProgramArguments</key>
<array>
<string>postmulti</string>
<string>-i</string>
<string>postfix.in</string>
<string>-p</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<key>KeepAlive</key>
</dict>
</plist>
Copy this into /Library/LaunchDaemons/ as org.postfix.in.plist
b. Enable service
sudo launchctl enable system/org.postfix.in
Doing this marks the LaunchAgent as active/executable. This means it will be started on boot/reboot, if so configured.
Check if the new LaunchDaemon is enabled:
sudo launchctl print system | grep postfix
Check which are enabled ("false") or disabled "true")
sudo launchctl print-disabled system
d. Start service:
(Only necessary if starting after boot. The service should start fine during boot.)
sudo launchctl kickstart system/org.postfix.in
Check if running:
sudo postfix status
7. (Alternate - deprecated) Configure a LogoutHook
At this time, I have not found a convenient way to call a script on logout. MacOS still provides LoginHooks and LogoutHooks. Develpers have been warned that these are deprecated and may be removed but, at this time are still functional. A clean solution for scripts/commands to be run at logout, shutdown, restart, etc. using launchd has no been documented.
So, to stop the posfix-in instance on logout, you would:
a. Create the LogoutHook:
sudo defaults write com.apple.loginwindow LogoutHook /path/to/logoutscript
b. Create the logout script:
#!/bin/sh
postmulti -i - -p stop
This is not optimal because the postfix.in instance is an MTA for all accounts on the mailserver and runs as root. If user1 logsout then user2 logs in to the same Mac, user2 will not be able to check his mail.
Much simpler. Although, again, spinning up a local mailserver for users in the network is preferred. The users can interact with email with the client of their choice and the employer can control and preserve the internal emails as is sometimes required by law or may become very important for HR.
Posted by Gordon, No Hair Github Pages, April 12, 2020; revised March 30, 2022.
© nohair.net and the author
For comments, corrections, and addenda, email: gordon[AT]baskin.network