Disposable Email Addresses with Postfix

As I thought about setting up my website and email, I wanted to have a way to give out disposable email addresses. That way, I can give Widget Co an email address unique to them, and I can know if they sell my email because I will get emails from Sprockets Inc. at my address for Widget Co. In that case, I can trash all email sent to that address, eliminating that spam.

A Possible, but not Ideal Option

I know of a couple people who use the following system: they set up a catch-all email for their domain, and point it at their main inbox. Then, they give name@domain.com to people they want to communicate with, and they give business_name@domain.com to businesses they need to communicate with. In my example, they would give widget@domain.com to Widget Co.

However, there are a couple problems with this system.

  1. Spam. They receive all the spam that is sent to any address at their domain. Granted, they could use spam filtering to solve this, but wouldn’t it be better if it just bounced?
  2. Plausible deniability for the company. It is conceivably possible that a spammer could have made up that address from a dictionary, or someone could have done so deliberately. I would prefer to have a stronger reason to claim that a company sold my email.

A Better Option

Another option would be to set up an alias pointing to the main email for every company you want to communicate with. This has the advantage of dramatically reducing the spam problem, and depending on how creative you get with the addresses you give out, it could potentially address #2 above as well.
However, this option has a flaw that caused me to write it off. That is that you have to manually create an alias for every address you want to give out. The huge advantage of the first option is that you don’t have to pre-plan or keep a list of the addresses you give out.

My “Ideal” Option

As I thought about it (this is before I even had a domain name, so it was purely theoretical), I came up with the idea that maybe I could combine the company name with some sort of hash of the name and a constant secret, so that the disposable email would only be delivered to my inbox if the hash matched its expected value.

That seems hard to read, so I’ll describe the process. Let’s say that we want to give Widget Co an address. We choose widget as the name for the email. We have pre-picked the secret "S3cr3t!!". So to determine the address, we take hash(name + secret), in this case, hash(widgetS3cr3t!!) and get something like 7ab5w9274abe (made up). We then make the email address name+hash@example.com, which in this case is widget+7ab5w9274abe@example.com.

When the mail server gets a message like this, it first checks the regular address list, and if it doesn’t match, it checks the disposable addresses. It performs the hash the same way we did above, and accepts the message if it matches, and bounces the message if it does not match.

This solves the #1 problem from above, and also partially addresses #2, depending on the strength of the hash.

Weak Points

  • Length of the email address. If you already have 6 characters, and you add more to it, you end up with a really long address. I worry this may cause problems with length limits on web forms.
  • Potential brute force. There is not enough room in the email address to use a full hash. That would really be too long, making the above problem worse. Bitcoin, for instance, to mine a block, requires one to find something with a hash with a certain number of zeros at the beginning. I think they’re up to 6 or 7 at this point, which means it is possible to brute force the first 6 or 7 digits of a hash (whichever one Bitcoin uses). This means that 6 or 7 digits of a hash is brute forcible, making this scheme brute forcible.

My Final Choice

I ended up implementing a variation on the above choice, using the first six hex digits of a SHA-256 hash. My choice of six digits is an attempt at a decent tradeoff between the two weak points from above. Yes, it is probably possible to brute force the secret, but the reward for someone using brute force is very low; I will just delete their spam. If your email is more important than mine, you may want a better option altogether.

I also added the concept of a “prefix” to the above option. That enables me to have different sets of disposable addresses if I need to, with potentially different secrets or mailboxes.

So the final address ends up being the concatenation of the prefix, the name, a plus sign (for easy separation), and the first 6 characters of the SHA-256 hash of the name plus the secret associated with that prefix. For example: q1_widget+0d3343@example.com.


When I set up my mail server, I made this a secondary concern. I wanted email to work, then I would worry about fancy stuff. I had already decided to use Ubuntu Server, because I was already familiar with it. I found two decent tutorials, and I combined what in my opinion were the best parts of them. I used the tutorial Lee Hutchinson wrote for Ars Technica, How to run your own e-mail server with your own domain, along with the tutorial from Linode, Email with Postfix, Dovecot, and MySQL. I mostly used the Ars Technica tutorial with the exception that I followed the advice from Linode’s tutorial to store your users, password, and aliases in a MySQL database. There is merit to both options, but I was already going to need MySQL for WordPress, so it didn’t cost me a lot of resources.

I’m not going to rehash those tutorials, because they did a far better job, and it is out of the scope for this (already long enough) blog post, but I will explain the parts relevant to disposable addresses.

I’d like to start off with a disclaimer that this is a really ugly way to do this, and there is probably a better way to do it, but it does work.

If you follow the advice from the Linode tutorial I linked, you will have a table called virtual_domains that tracks domains, a table called virtual_users that tracks all your real email accounts, and a table called virtual_aliases that tracks your aliases (PSA: don’t forget to alias postmaster, webmaster, abuse, etc. to an actual account!).

I added a table called email_secrets to track the information associated with disposable addresses. It has four columns: id (auto_increment primary key), user_id (so Postfix can know where to put email for this set of disposable addresses), prefix (to match addresses with rows in this table), and secret (as mentioned earlier).

There is a weakness in this setup: it is not domain specific. If you try to handle multiple domains with one server, the disposable addresses will work at any domain, I think. If this is your use case, then modifying my setup according is left as an exercise to the reader.

However, now we need to get Postfix to use this. In the Linode tutorial, you created a file to inform Postfix about the email_aliases table. Then, you linked to it in the main.cf. We are going to add a second file to that rule. I called mine mysql-virtual-disposable.cf. It looks like this (usernames/passwords changed):

user = username
password = password
hosts =
dbname = your_database_name
query = SELECT vu.email FROM virtual_users vu INNER JOIN email_secrets es ON vu.id = es.user_id WHERE (instr("%s", concat(es.prefix,"_")) = 1) AND (mid(sha2(concat(mid("%s",char_length(es.prefix)+2,locate("+", "%s")-char_length(es.prefix)-2),es.secret),256),1,6)) = (mid("%s", locate("+", "%s") +1, locate("@", "%s") - locate("+", "%s")-1));

The important part there is the query, which I will explain in a minute.

Then, I had to change the main.cf line to

virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf, mysql:/etc/postfix/mysql-virtual-disposable.cf

This tells Postfix to first run the query in the mysql-virtual-alias-maps.cf file, and if it doesn’t get a match, to run the query in the mysql-virtual-disposable.cf file before bouncing it if neither match.

That query…

SELECT vu.email FROM virtual_users vu INNER JOIN email_secrets es ON vu.id = es.user_id
WHERE (instr("%s", concat(es.prefix,"_")) = 1)
AND (mid(sha2(concat(mid("%s",char_length(es.prefix)+2,locate("+", "%s")-char_length(es.prefix)-2),es.secret),256),1,6))
= (mid("%s", locate("+", "%s") +1, locate("@", "%s") - locate("+", "%s")-1));

I’m going to try to explain that query line by line. I must confess, I’m not super familiar with SQL. I don’t even know for sure whether you’re allowed to break the lines the way I just did, however, it works for purposes of explanation.

First of all, %s refers to the email address in question. I don’t know if Postfix uses prepared statements or not, but hopefully Postfix mitigates SQL injection… Someone please comment if you know.

Postfix expects to run this query and get one row and one column back. The result should be the email address for the real inbox the email should be delivered to. If it receives no results, it will bounce the email.

Line 1: we want to work with data from the tables virtual_users and email_secrets. We want the rows from email_secrets joined to the rows from virtual_users if the id’s match.

Line 2: This tests each row to see if the email starts with the prefix.

Line 3: This extracts the name from between the prefix that matched in line 2 and the +. It then concatenates the secret corresponding with the prefix and takes the SHA-256 hash. Then, it takes only the first 6 characters. (I apologize for the magic numbers. They account for off by one errors. This is true of the next line as well. Obviously the 256 refers to a SHA-256 hash, and the 1 and 6 refer to taking 6 characters, starting with the 1st position.)

Line 4: This starts with the equals sign, so that means that the result of line 3 (the first 6 characters of the hash) needs to equal whatever this finds. This line pulls out the hash by pulling the text between the + and the @.

If all of this succeeds, the WHERE clause will be true, and MySQL will select virtual_users.email (as we asked for in line 1) and return it to Postfix.


So, how does this work, in practice?

To start out, you need to add a row to the email_secrets table. You will need to include the user_id for the mailbox you want the email to be delivered to. You need to supply a secret and a prefix as well. Keep the secret reasonably secret; it allows you to generate disposable addresses.

Io generate an email address, you first put the prefix and name, then you take SHA256(name+prefix), for instance, SHA256("widgetS3cr3t!!") and put a plus sign, then the first 6 hex digits of the hash, then @yourdomain.com. For instance, q1widget+3eda42@example.com.

To do this, you will need to somehow take that hash. Personally, I use the Linux shell and do:

echo -n "widgetS3cr3t!!" | sha256sum


I have outlined a method for creating unlimited amounts of disposable email addresses if Postfix is your mail server.

If this helps you, or if you have a question, please comment below. I moderate comments, so it may take a day or two for your comment to appear.

7 thoughts on “Disposable Email Addresses with Postfix

  1. Great post! A couple of questions on implementation:
    * How do you configure your mail client to use the correct From and Reply-To addresses depending on the person you send mail to.
    * Do you have a solution for mail between more than two people? You may decide to never include more than one address in the recipient-list. But if one of your friends sends an email to you, and includes a couple of buddies in the Cc field, then they all have one of your valid email addresses, that maybe you did not intend them to have access to.
    Nevertheless I would not regard this behaviour of your friend as unwanted or unnecessary. But if this email address ends up on a spam list, you can no longer blame your friend, because maybe one of the buddies is responsible.

    I would like to hear if you know about a solution. I could not find one on the interwebs.

    1. Thanks for the reply! Yours is literally the first non-spam comment I’ve ever received. Glad to know someone found my ramblings interesting :)

      To answer your first question, I usually don’t. I implemented this scheme so that if a company sells my address, I can just blackhole it. If I’m emailing another person, I just use my regular address.

      However, every now and then, I want to email a company back from their unique address. I use Thunderbird, and it has a feature where you can have multiple “identities” per email account. It’s under Account Settings, then click on the you@yourdomain.com, then “Manage Identities…” is the last button above OK and Cancel. I add another “identity” with the disposable address, then I can choose it when I send an email.

      There’s also a way to do it on iPhone. In the Mail, Contacts, Calendars settings, you can click on the account > Account > Email > Add Another Email.

      Second question: No, I don’t. I only use it for companies. I realize eventually I’ll have to rely on my spam filter, but at least this way I can detect if my address is literally sold.

      The only solution I can think of is this: I wouldn’t recommend it, but it was an interesting thought exercise, so I’ll post it anyway. You could, in theory, set up some sort of email filter that makes the disposable address specific to an address, but that would require a database to keep track of. And a team of people to keep up with… Utterly ridiculous, but it’s the only one I could think of off the top of my head.

      I should probably do a followup post with a few things I’ve figured out since I posted this. If I do, I’ll include a screenshot of the relevant page in Thunderbird.

      Side note: Sorry, I don’t have Markdown commenting yet. Having just typed this comment, I’m thinking I’d better find out how to implement it…

  2. Dear Zack, thanks for your reply! I don’t necessarily need Markdown. Don’t worry (-;
    Anyway, I knew that you could set up these identities. I just wondered if there was a way to do this automatically, and also automatically select the correct one.
    Concerning your database idea: I think it is doable. Whenever you generate an email address, you also assign to it a correspondent email address, and this is added as entry in the database.
    Alternatively, if a valid disposable email address receives an email address for the first time, the mail server automatically records the From address (or the Reply-To), and adds an entry in the database. From then on, only this address can send email to the disposable email address.
    But then the problem remains how to use this with groups of recipients. But your solution is probably the most pragmatic: just use a regular email address, and use the disposables for companies.

    Thanks, best, jmc

    1. Well, as far as I can tell, in Thunderbird, if you reply to an email sent to an address, it defaults to replying from that address if you already have the identity set up. I doubt it will create them automatically.

      What you’re describing, is conceptually the same as if you were to have to give each sender a unique token to be able to send you email. That seems to be against the spirit of the concept of email, but also sounds like it would reduce spam…

      Of course, thinking about it, you could extend the concept to a whitelist based on sender, then a whitelist based on a contact list, and then just using that as a spamfilter signal. Then you have basically every webmail service in existence today. Obviously that’s a large step, but I figured it was worth mentioning. Sometimes I wonder if I’m not reinventing the wheel poorly.

      Thanks for commenting!

  3. Great Post. I have been working with the following combination:

    – Catchall email for one of my domains
    – Thunderbird add-on Virtual ID

    Regarding the 2 points you make 1) Spam – this is an issue. I get a fair bit of spam even with spam tools. However, I don’t really don’t have a challenge with #2 point. Even if I had this concern, the convenience of creating zackorndorff {a’T }} skintag [d’o’t] org on the fly when standing in front of a counter or over the phone over weights the need for a better identification solution. Not to mention that this solution makes it easy for my wife to deal with this solution.

    The biggest issue I have with the solution is the mobility of the solution. For example, if I want to send an email my iPhone will use the catch-all address OR I have to wait until I get back to my computers (Thunderbird w/add-on). I have for some time thinking of a better solution that I could implement a virtual identity solution on my email server. The challenge (from a code dev, time and management perspective) is the management of the solution on a mobile device.

    The key requirements of the management solution would be:
    1) To keep the association of the created email to the sender’s email and re-write the TO: based on the existing association in the email server. This is so standard email clients can be used easily.
    2) To be able to send an email without an existing association (association creation on the fly). Possibly use some email body keywords. This could be implemented like controlling lists on a listserv. E.g. FROM::disposable@ in the first line.
    3) Easily blackhole email addresses (from mobile device and computer). Possibly again like a listserv. Reply spam to block.

    Thanks for a place to put some thoughts out there!

  4. Hi Zack,

    great post. Been looking around for a while. Would you be able to tell me how to set up postfix so that a database is updated when it first sees an email address? I would like hand out an email address the moment I get asked without first having to manually ad it to a database.

  5. Hi there are using WordPress for your blog platform?
    I’m new to the blog world but I’m trying to get started and set up my own. Do you need any
    coding expertise to make your own blog? Any help would
    be greatly appreciated!

Comments are closed.