fully local testing of email sending web apps (on Mac OS X)
Attention: extreme OS X geekery ahead. This is for people who actually know what Terminal.app is and use it.
You are developing a web application, possibly written in PHP, and you are doing this on your local Mac under OS X (10.4/Tiger in my case). Setting up a local apache2/php/mysql system is quite eassy with tools like MAMP, which is what I use for all y work. The fact that OS X is a real Unix (Darwin, BSD based Unix) and has quite a lot of the Unix commandline utiities built in helps a lot.
But when it comes to the web application sending out emails (for invites, account verifications, notifications etc) I was stuck.... until today. You see, PHP's mail() function does work on OS X... only you never see the emails. My recent forray into postfix mailserver setup on my Linux webserver helped me see how this all comes together. Mac OS X comes with Postfix installed and fully configured. And it actually does it's thing too. But looking into the logfile at
/var/log/mail.log showed some interesting things: Postfix was actually trying to deliver my test emails from the web app! Only due to the way spam is often generated from zombie'ed desktop PC's on dynamic IPs (dial-up, aDSL etc) most real mailservers simply reject any outgoing mail from such a machine. My mails were being process, but couldn't be delivered.
Checking the system user's mailbox in the terminal quickly showed a number of MAILER-DAEMON messages. You can normally only access these messages via the commandline tool 'mail', more about that later.
If you try to send yourself a local mail, you will see that this works just fine:
serenity:~ martin$ mail martin Subject: Test mail for the local user This is me in Terminal mode . EOT serenity:~ martin$ mail Mail version 8.1 6/6/93. Type ? for help. "/var/mail/martin": 2 messages 1 new 1 MAILER-DAEMON@sereni Sat Jun 28 18:09 13/530 "DON'T DELETE THIS MESSAGE -- FOLDER INTE" >N 2 firstname.lastname@example.org Sat Jun 28 18:44 14/527 "Test mail for the local user" & q Held 2 messages in /var/mail/martin serenity:~ martin$
You could even try this with an actual email address of your's... but that may or may not succeeed due to the way ISPs block them or not (see above=.
Now for testing, we want a reliable way to test the sending of emails, and we are not really interested in the finer details of Real Time Blacklisting, Greylisting or SMTP-AUTH. We want our web app to generate an email, an d we want to see it in out desktop email client (the way an end user would). But we also want to do this OFTEN, and we are trying to DEBUG the process in the web app... so we might possibly be doing this very often indeed, and need to see the results immediately too.
From my Linux server setup experiences I had learned a very nice feature of Postfix (and any other MTA that's wirth it's source code): Aliassing and virtual users. I had last cofigured Postfix so I became curious if I could use that skill in OS X too.
Here's the idea:
- Use Postfix'
virtual_alias_mapsto create any number of 'virtual' email addresses, that would ALL end up in one email account. Ideally that account would be a local system user's account mailbox.
- As a second step, make it possible to access that system account mailbox through a desktop mail client like Thunderbird or Mail.app
Step one, Postfix with virtual_alias_map:
Open up a Terminal.app window (you don't have one open already?!) First we create a file called 'virtual' that will hold our alias definitions (on OS X 10.5 this file already exists btw):
serenity:~ martin$ cd /etc/postfix/ serenity:/etc/postfix martin$ sudo nano virtualThe alias definitions on my machine look like this;
@test.local martin @webapp.dev martinThis will tell postfix that ANY address in the 'test.local' or 'webapp.dev' domains should be put into my local iser's account mailbox (You will need to replace martin with your system short name). I could have created the aliases explicitly by writing full addresses, but for our pirposes the 'catch all' form is exactly what we want.
Next we need to 'hash' this table into a form that is quicly readable for the postfix programm (to save on startup time etc)
serenity:/etc/postfix martin$ sudo postmap /etc/postfix/virtual
And then we need to tell the actual postfix about our new virtual_alias_map:
(You could do this by editing the postfix config file directly, but I recomend using the postconf tool)
serenity:~ martin$ sudo postconf -e "virtual_alias_maps = hash:/etc/postfix/virtual"That's actually all there is to this trick. It already works now. Try it by sending a mail to any address in the 'virtual domains' you just created.
mail email@example.com so on. Thest witj
Step Two: reading your system mailbox in a desktop mail client
Now what we really want is to be able to read/view our test emails from the web app (or the commandline) in Thunderbird, Mail.app or the mail client of your choice. To do this, we need what is called a POP3 server running locally and serving up the system mailbox as a POP3 account. (We could do this with IMAP too btw, but let's keep it simple shall we) Again the before linked article has it all, albeit in a security aware mindset whcih over complicates matters for our purely local testing purposes (you will want to make sure you do not leave this POP3 server running if you don't need it though)
This ZIP (http://www.macosxguru.net/downloads/localmail.zip) contains a pre compiled OS X binaries of UW's (University of Washington)
imapd. Download the ZIP and unpack where you want (I use ~/localmail/ in this example). Then we will setup the POP3 service:
Copy the executable to a good place:
serenity:~ martin$ cd localmail/ serenity:~/localmail martin$ sudo mkdir -p /usr/local/libexec serenity:~/localmail martin$ sudo cp ipop3d /usr/local/libexec/Make it execuable and set the riht permissions:
serenity:~/localmail martin$ sudo chown root:wheel /usr/local/libexec/ipop3d serenity:~/localmail martin$ sudo chmod 755 /usr/local/libexec/ipop3dNext we want to 'create a loadable service' . for this we copy the .plist for pop3 from the ZIP:
serenity:~/localmail martin$ sudo cp edu.washington.pop3.plist /Library/LaunchDaemons/edu.washington.pop3.plistAt this point we have a loadable POP3 service, so lets test it:
sudo launchctl load -w /Library/LaunchDaemons/edu.washington.pop3.plist
To quickly test this, we try to connect to port 110 (pop3 default) on localhost and see what happens:
serenity:~/localmail martin$ telnet localhost 110 Trying ::1... Connected to localhost. Escape character is '^]'. +OK POP3 localhost 2004.89 server readyNow isn't that sweet? Let's talk to it:
user martin +OK User name accepted, password please pass mypassword +OK Mailbox open, 1 messages quit +OK Sayonara Connection closed by foreign host.Oh Kay! Now we can create a new account in our desktop mail client(s) of choice. The interesting parts are "incoming mail server: type POP3, address: localhost, user/account: yourshytemshortname." And that's about all there is to it. Simple once you cut away all the complexity :)
This solution is by no means perect or exaustive. The sole goal of it is to be able to reliabbly and locally test sending email from a local web app and debugging the result.
- When you change/add stuff in the /etc/postfix/virtual file, make sure to re-hash it by doing
sudo postmap /etc/postfix/virtual
- There's no need to start/stop or reload the postfix process as you would on a real mailserver. In the OS X setup it's not actually running all the time, and will load all cofiguration on startup (on demand, as soon as you send mail through it)
- like any postfix inuestallation, OS X has a mailque, and esp a 'deferred' mailqueue. That means mails it couldn't deliver will be re-tried - in theory. Due to the fact that in OS X it's not actually permanently running, the re-tries will happen the next time postfix is active - when you bext send mail through it etc. Another way to 'force' re-try of delivery is to
sudo postfix flush
- some mails won't ever be delivered (watch mail.log for hints), and you might want to get rid of those. Use
sudo postsuper -d ALLto delete ANY mails currently still in the queue
- you can watch the mail.log by either using
tail -f /var/log/mail.logor by openening it in 'Console'
- Shutting down the ipop3d is a convenient as starting it:
sudo /bin/launchctl unload -w /Library/LaunchDaemons/edu.washington.pop3.plist
- The binaries in the above linked ZIP are suitable for both Intel and PPC Macs
- Have Fun!
- developing for PeopleAggregator on MacOS X
- back up, but not from backup
- playing OpenSocial - in your own sandbox
- collaborative plotting for large groups
alles Bild, Text und Tonmaterial ist © Martin Spernau, Verwendung und Reproduktion erfordert die Zustimmung des Authors