The trick that I use depends on the fact that I have my own domain. That means I can run sendmail on my computer, and I can create email addresses quickly and easily. I will use the domains example.com, example.net, and example.org for this document, as recommended in RFC 2606.
The basic idea is this: instead of having one email address, I have dozens. I create a new email address for every person with whom I exchange messages, as well as addresses for websites and companies when necessary. If an email address is accidentally revealed, or if one of the companies decides to start sending annoying amounts of unsolicited mail, I simply expire the email address and, if desired, contact the sending party to tell them about the new address. I don't have to contact all of my friends whenever I turn off one address, only the one person who uses that address to talk to me.
OK, how is this implemented? There are two things I have to do. First, I need my sendmail to accept the messages for the active addresses, and send them all to me. Second, I have to ensure that my outbound email has the correct Reply-To: address for the particular recipient of the message.
If you're familiar with sendmail, you can probably guess how I do the first thing. I set up a virtual user table. Here's the sendmail.mc file used to make this work:
Then, I create a file called /etc/mail/virtusertable.src. It contains entries similar to this:
VERSIONID(`sendmail.mc for example.com version 01')
FEATURE(`virtusertable', `hash /etc/sendmail/virtusertable')dnl
FEATURE(`genericstable', `hash /etc/sendmail/genericstable')dnl
FEATURE(`access_db', `hash -T<TMPF> /etc/mail/access')
The addresses I create for regular correspondance are just successive numbers, plus an unpredictable sequence of two characters to avoid dictionary attacks.
firstname.lastname@example.org error:nouser Spammers found this address
email@example.com error:nouser Spammers found this address
Now, recall that sendmail doesn't read the virtusertable.src file, it reads another file called virtusertable.db. I've got a little Makefile in /etc/mail that I use to keep things up to date:
Now, I can change the virtusertable file, and when it looks correct, issue (as root) the command:
all : genericstable.db virtusertable.db mailertable.db aliases.db access.db
%.db : %.src
makemap hash $* < $<
aliases.db : aliases
hup : all
killall -HUP sendmail
This will update the appropriate database file, and send a SIGHUP to sendmail, telling that program to reload its databases.
make -C /etc/mail hup
So, that's the receiving side. How about sending? There may be a way to configure sendmail to rewrite the outbound addresses according to a database of recipients, but I haven't figured one out. Instead, I have written a bit of code for my email client, which is rmail mode in Emacs. Here are the relevant bits of Emacs Lisp:
What this does is to insert a hook into the mail system when I hit send. A bit of elisp locates the email address in the "To:" field, and tries to match that string to one of the names in the 'outbound-address-alist'. If it finds a match, it inserts the corresponding data into the "Reply-to:" field. If no match is found, or if there are multiple recipients, it uses the default fallback address.
(setq user-mail-address "firstname.lastname@example.org")
(setq mail-specify-envelope-from t)
(setq full-name "Winter Toad")
;; a function to parse out the header and send email as if from
;; different usernames. That way, I can obsolete a username if it
;; gets spam.
(narrow-to-region 1 (mail-header-end))
(expand-mail-aliases 1 (mail-header-end))
(re-search-forward "^To: ")
;; parse out the recipient address
(let (recipient from-whom)
((looking-at "\\([^ \\t]*\\)$")
(setq recipient (match-string 1)))
(setq recipient (match-string 1))))
(setq from-whom (or (cadr (assoc recipient outbound-address-alist))
(cadr (assoc nil outbound-address-alist))))
(insert "From: " full-name " <" from-whom ">")
(re-search-forward "^Reply-to: ")
(let ((namestart (point-marker)))
(kill-region namestart (point-marker))
(narrow-to-region 1 (1+ (buffer-size)))))
It also sets the sender address to email@example.com, which means that automated replies, such as sendmail daemon warnings and errors, will be delivered to that address. It should be redirected in the virtusertable to some appropriate address so that you can be notified of problems at the recipient's end (though many systems no longer generate bounce messages, because of spam abuse).
Anyway, with all this, I get really no spam. Every few months I may get one message on one of my email addresses, typically one that I used for a forum post or to send a bug report or patch to a mailing list. I retire the address, set up a new one, and never get spam at that address again.
Some time later I'll describe the cryptographic certificates in the mail configuration, and how they allow secure relaying.