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
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 martin@serenity.loca  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:

The first part is suprisingly easy to accomplish, and requires none of the complex geekery you will find when googlig for solutions. I actually based all my solution on this one here. By neglecting the securuty aware bells and whistles of that solution (TLS/SSL secured login etc) the resuklt is suprinsingly simple. (As we are doing this as a purely local test environment, and turning it on only when we need it, there's really no sense in the very complex security setup needed)

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 virtual
The alias definitions on my machine look like this;
@test.local    martin
@webapp.dev    martin
This 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 anyone@webapp.dev and so on. Thest witj mail to see that they actually got put in your system mailbox.

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) ipop3d and 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/ipop3d
Next 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.plist
At 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 ready
Now isn't that sweet? Let's talk to it:
user martin
+OK User name accepted, password please
pass mypassword
+OK Mailbox open, 1 messages
+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 :)

Some notes:
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.


<< Who would not rather be a rising ape than a falling angel?  |  identifying >>

alles Bild, Text und Tonmaterial ist © Martin Spernau, Verwendung und Reproduktion erfordert die Zustimmung des Authors

Martin Spernau
© 1994-2024

amazon.de Wunschliste

Facebook me!


powered by Traumtank