How to email FROM specific IPs, using Linux and Postfix
I was asked recently to find an email solution for a new marketing firm. The solution specifically, was for the problem of outbound IP binding. That is what IP address email is sent from. If you're providing email outsourcing for multiple clients, it's important to keep each client on it's own IP(s) so one being blacklisted won't affect another.It's a simple matter to add IP addresses to a server for the purposes of listening, but to tell a mail server to send from a particular IP(s) (and not just the default IP) can be a pit tricker. Depending on the resources available to you though, this option really isn't hard. We started by looking at Qmail, as it's a popular choice, but we ran into a number of problems applying a patch to allow it to dynamically bind to an IP per outgoing email sender domain. In the end, I built a solution using multiple Postfix instances, that each bind to the appropriate IP. You're going to end up with: smtp1.domain.com smtp2.domain.com etc.
The first thing you'll need is a working server. That's well beyond the scope of this, but for the record I sandbox in VMWare Server using CentOS 5, with MySQL, Apache, VIM, compiling tools and so on. Second thing you'll need is a working Postfix installation. This took me a long time at first, due solely to a formatting error step 4, below. That was very frustrating.... but if you learn from my mistakes you'll have an easier time. Trick is to format the make command just right.
1. Grab the Postfix source, and untar. I used the guide here for the install, it's got all the basics that you need. You should really follow along in that, and turn here if it's instructions are a little spartan. 2. You'll need to ensure MySQL and MySQL-devel are installed. My finished Postfix instances didn't use them (because they weren't receiving mail, just sending), but I couldn't see how to compile without the MySQL step. I had to reboot after installing MySQL-devel. 3. Run:
make -f Makefile.init makefiles 'CCARGS=-DHAS_MYSQL -I/usr/include/mysql' 'AUXLIBS=-L/usr/lib/mysql/ -lmysqlclient -lz -lm' make
4. If steps 3 throws errors, you're going to have to hunt for a solution. Once successful, run the following. This will build makefiles, using the following directories for data, config, and queues. Adjust as necessary. (Bear in mind, multiple Postfixes only need a single set of binaries, but multiple conf and spool folders):
make makefiles CCARGS='-DDEF_CONFIG_DIR="/etc/postfix1" -DDEF_DATA_DIR="/var/lib/postfix" -DDEF_QUEUE_DIR="/var/spool/postfix1"' make
5. If step 4 was a success, you just cut 4+ hours out of my initial setup time. Congrats. Now you need to create some users and groups. I use Vim to edit the conf files. If you don't know what the following does, best to check up on users and groups principles.
vim /etc/passwd: [insert] postfix:*:12345:12345:postfix:/no/where:/no/shell
vim /etc/group: [insert] postfix:*:12345: [insert] postdrop:*:54321:
5. Now you get to install! Lovely. The default options were fine for me all the way through (this is dependent on the makefiles we made earlier).
make install
6. You'll need to add a hostname (or two?) at this stage, as Postfix wants a hostname, not an IP when you configure it in the following steps. Again, I use Vim, and obviously use your own IP here. You may want to use smtp1.domain.com, or whatever else:
vim /etc/hosts [insert] 192.168.0.1 host.domain.com
7. Now you need to configure main.cf with the default minimum options. Configure master.cf with default options as well. (The hostname you configure Postfix with is the one you just entered into the hosts file). Note that this scenario has you binding Postfix to different IPs, so the advise to comment out a line in master.cf (if sending only) does NOT apply. Also, add the IP you wish to bind to (mail will come FROM this IP), to main.cf:
smtp_bind_address = 192.168.0.1
8. Start that sucker:
postfix -c /etc/postfix1 start
Once that's up and running and you've tested it (also a good time to check the SPF records of your domains), you can go get yourself a beer. The hard part is over. You'll also need to create a startup script for that Postfix instance. See below for a sample.
Now you can create any number of additional Postfix instances quite simply. I used the guide here, andI'm pasting below my stripped down version again (for my own records, more than anything).
9. Copy the conf files to a new directory:
cp -rp /etc/postfix1 /etc/postfix2
10. Adjust the new conf to look at a new spool directory, which you will need to create. Then have Postfix check things and create files as necessary:
vim /etc/postfix2/main.cf [edit] queue_directory = /var/spool/postfix2
mkdir /var/spool/postfix2 postfix -c /etc/postfix2 check
11. Edit the conf of the original Postfix instance to see this new set of conf files, so the Postfix daemon will load them as well:
vim /etc/postfix1/main.cf [insert] alternate_config_directories = /etc/postfix-out
12. Now, edit the new conf file again, to adjust the hostname and the IP it's going to bind to, and you're just about done.
vim /etc/postfix-out/main.cf [edit] myhostname = smtp3.domain.com [edit] smtp_bind_address = 192.168.0.2
13. Now you'll need to create a new startup script for the new instance. Here's mine, for CentOS/RedHat. It assumes that Postfix binaries have been installed with the defaults from the install and any instance specific folders (conf, and spool) are "postfix2". You can just run a find and replace to adjust the postfix2 path to postfix3 (or to postfix1, if this is your first startup script), and so on.
#!/bin/sh # # postfix2 # Postfix second instance for Redhat Linux and CentOS # description: Postfix is a marvelous SMTP server.
# Source function library. . /etc/rc.d/init.d/functions
# Source networking configuration. . /etc/sysconfig/network
# Check that networking is up. [ ${NETWORKING} = "no" ] && exit 0
[ -x /usr/sbin/postfix ] || exit 0 [ -d /etc/postfix2 ] || exit 0 [ -d /var/spool/postfix2 ] || exit 0
RETVAL=0
start() { # Start daemons. echo -n "Starting postfix2: " if [ ! -e /var/spool/postfix2/etc/resolv.conf ]; then cp -f /etc/resolv.conf /var/spool/postfix2/etc fi /usr/sbin/postfix -c /etc/postfix2 start 2>/dev/null 1>&2 && success || failure RETVAL=$? [ $RETVAL -eq 0 ] && touch /var/lock/subsys/postfix2 echo return $RETVAL }
stop() { # Stop daemons. echo -n "Shutting down postfix2: " /usr/sbin/postfix -c /etc/postfix2 stop 2>/dev/null 1>&2 && success || failure RETVAL=$? [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/postfix2 echo return $RETVAL }
reload() { echo -n "Reloading postfix2: " /usr/sbin/postfix -c /etc/postfix2 reload 2>/dev/null 1>&2 && success || failure RETVAL=$? echo return $RETVAL }
abort() { /usr/sbin/postfix -c /etc/postfix2 abort 2>/dev/null 1>&2 && success || failure return $? }
flush() { /usr/sbin/postfix -c /etc/postfix2 flush 2>/dev/null 1>&2 && success || failure return $? }
check() { /usr/sbin/postfix -c /etc/postfix2 check 2>/dev/null 1>&2 && success || failure return $? }
restart() { stop start }
# See how we were called. case "$1" in start) start ;; stop) stop ;; restart) stop start ;; reload) reload ;; abort) abort ;; flush) flush ;; check) check ;; status) status master ;; condrestart) [ -f /var/lock/subsys/postfix2 ] && restart || : ;; *) echo "Usage: postfix2 {start|stop|restart|reload|abort|flush|check|status|condrestart}" exit 1 esac
exit $?
14. You can start your new instance with:
postfix -c /etc/postfix2 start
15. Test again mate!
16. Once that's done, you're gold. You can repeat steps 9 - 15 for additional Postfix instances.
Bonus: I use PHPlist for subscriptions and mailing. To configure PHPlist to send using a particular SMTP server, look for this configuration directive (near the end of config.php). Any number of PHPList instances can now be configured to use the appropriate SMTP servers, keeping each one separate from the other, and passing SPF records and keeping spam-rule friendly.
define("PHPMAILERHOST",'host.domain.com');