cPanel & WHM Version 114 has been released, and brings a slew of great updates. Take a look at what is included, and then upgrade today!
This object is in archive! 

Email sub-addressing (plus addressing)

Tomas Gonzalez shared this idea 10 years ago

I'd like to be able to configure email sub-addressing on my WHM.

It'd be OK if it's disabled by default, but I want to be able to simply change some config on WHM to enable it.

The objective, in my case, is to have my server receive emails with IDs so my app can handle them appropriately. Ie.

Best Answer

cPanel & WHM v58 has just gone to CURRENT and includes this functionality. If you have any questions let me know, or if you experience any problems with it, please do submit a ticket and let us know!

Replies (23)


I fully support this idea. However, I'd like to expand it a bit.

I'm gonna use Gmail's plus addresses in my examples...but this applies to cPanel/WHM domains too.

Currently, with Gmail, you can give out addresses like...

    "username" obviously being the username

    "plusaddress" being ANY string (or "any string not including an @ (at) sign")

...this works fine, when intelligent people make websites, however -- & far too often -- some websites are written by someone that doesn't know that + (plus) is a VALID char in an E-mail address, so that website rejects "" as being an invalid E-mail address, even when it is perfectly valid.

I've been planning on suggesting this to Google, for Gmail, but since this issue came up, I'll suggest it here 1st.

I want "plus addressing via subdomains". Continuing the Gmail example...

A user ("username") would be able to set-up...

...which would be exactly the same as...

...except, it would work on those sites with bad webmasters (where + chars are considered invalid), since in this format, there is no + char!

Following up on that, since "" is simply too easy to guess/reverse-engineer (a human can figure out how to re-arrange the E-mail address & still spam the user), I'd want the user to be able to turn off "" & enable ""...the "j45kd9dl4l" being any random string the user wants/chooses. The point here, is that no-one can tell from "j45kd9dl4l" that it points to the "username" user. Then that person gives out an address like...

...or more specifically...

...that bad site has a valid E-mail, but no-one can reverse engineer it to the real E-mail address.

The user should be able to choose one of the following behaviors for the subdomains they create...

  • Whitelist (everything is rejected {at SMTP time}, until they add a new address {part before the @ sign} to the whitelist)
  • Catch all (anything will go to thier E-mail, unless blacklisted) either case, there should be a blacklist, so if a plus address starts getting spam, they can turn it off (by deleting the whitelisted address...or, in the case of "catch all", adding a blacklisted address). Each user should be able to create (at least a few {3?}) subdomains...for example, the user "username" could create "", "" & "" & they would all be routed to the user "username"'s E-mail box...& they can enable, disable or rename those subdomains at any time.

Remember: this example used Gmail, since this idea was originally for Gmail (& still is). Simply replace "" above with "" in the case of cPanel/WHM.

This should be a setting cPanel users can enable -- or perhaps even just webmail users -- basically anyone with an E-mail address (regardless of their cPanel/WHM account-having status) should be able to login somewhere, turn this on & manage it -- but perhaps the subdomain-allocation should be handled at the cPanel or WHM user level? -- however, when it is turned on at that level, the "subdomains" should be handled by a wildcard in DNS & then just handled by Exim (or whatever implements this new feature), so you don't need a new DNS entry for every user's subdomains.

So, a cPanel (or WHM) user turns the feature "on", or more specifically, one of the following...

  • Disabled (no one can use plus addresses)
  • Default off (people can use plus addresses, but it's not on by default)
  • Default on (people can use plus addresses, it's on by default, but users can turn it off for their E-mail address)

...then individual "users" (anyone in control of an E-mail address) can manage it -- or turn it off for their address.

I'd also wanna be able to enable/manage this behavior for the case of Forwarders, they're not real "account"s, but they are E-mail addresses, so I'd wanna be able to manage it somewhere (probably while logged in as a cPanel user) -- so even Forwarders can be protected by plus addressing.

That was incredibly long, & perhaps it should be split to its own idea (which is fine), but it's extremely related to this, so I put it here.


I don't think it's useful to go further than GMail, but I definitely think that plus addressing for cpanel would solve a lot of problems for me.


Plus addressing is a powerful anti-phishing tool for users. I've been using it for about 15 years now, and I'd never go back. Gmail and both support it, among others. I wouldn't be willing to use a cPanel-managed domain for my email if I hadn't figured out how to implement it with baling wire, chewing gum, procmail, and perl. Subaddressing is becoming a standard feature of email systems; it's boggling to me that cPanel doesn't support it yet.


I would like to have the same thing, but Qmail-style DASH addressing, instead of using a Plus symbol.

client-465@example.comSame concept, with preference for the original "-" (hyphen as separator) instead of the "+" symbol used by Gmail.


We've had some requests for this and would defenetly like plus addressing. With + and not - as - doesn't mean a folder to most users. Unless this could be an option in the email filters


As we lose email customers to Gmail for issues like plus subaddressing, we risk losing customers, let alone customers for other services.

Our loss is cPanel's loss in the end; there's good reason for this feature to be considered.


For anyone interested in a little baling wire & chewing gum, account level filtering will deliver mail to a single address if you use a rule where To Matches Regex:


...where admin is the main address you want to accept subaddresses for. However, this only works for local mail delivery, not for mail from external servers.


Some nice ideas here.

What do you think cPanel? When can we get some of it?


There was a script posted here:

That sortof worked, but I had CPanel complain each day about the config file having been changed.

Instead, I applied the same changes manually. I went to WHM, Exim Configuration Manager, then the Advanced Editor tab and set some of the variables as shown below.

The variables in the editor allow you to insert new rules but you can't change rules that are already in the config file. What I did to overcome this was to use .ifdef and .endif statements to effectively comment out sections between configurable variables.

The list of things I changed was:


  1. #
  2. # Account level filtering for everything but the main account
  3. #
  4. central_filter:
  5. driver = redirect
  6. local_part_suffix = +*
  7. local_part_suffix_optional
  8. allow_filter
  9. allow_fail
  10. forbid_filter_run
  11. forbid_filter_perl
  12. forbid_filter_lookup
  13. forbid_filter_readfile
  14. forbid_filter_readsocket
  15. no_check_local_user
  16. require_files = "+/etc/vfilters/${domain}"
  17. condition = "${extract{size}{${stat:/etc/vfilters/${domain}}}}"
  18. file = /etc/vfilters/${domain}
  19. file_transport = address_file
  20. directory_transport = address_directory
  21. domains = +user_domains
  22. pipe_transport = ${if forall{/bin/cagefs_enter:/usr/sbin/cagefsctl}{exists{$item}}{cagefs_virtual_address_pipe}{${if match{${extract{6}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}}{\N(jail|no)shell\N}{jailed_virtual_address_pipe}{virtual_address_pipe}}}}
  23. reply_transport = address_reply
  24. router_home_directory = ${extract{5}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}
  25. user = "${lookup{$domain}lsearch* {/etc/userdomains}{$value}}"
  26. no_verify
  27. #
  28. # Account level filtering for the main account
  29. #
  30. # checks /etc/vfilters/maindomain if its a localuser (ie main acct)
  31. #
  32. mainacct_central_user_filter:
  33. driver = redirect
  34. local_part_suffix = +*
  35. local_part_suffix_optional
  36. allow_filter
  37. allow_fail
  38. forbid_filter_run
  39. forbid_filter_perl
  40. forbid_filter_lookup
  41. forbid_filter_readfile
  42. forbid_filter_readsocket
  43. check_local_user
  44. domains = ! +user_domains
  45. condition = ${if eq {${lookup{$local_part}lsearch{/etc/domainusers}{$value}}}{}{0}{${if exists{/etc/vfilters/${lookup{$local_part}lsearch{/etc/domainusers}{$value}}}{${extract{size}{${stat:/etc/vfilters/${lookup{$local_part}lsearch{/etc/domainusers}{$value}}}}}}{0}}}}
  46. file = "/etc/vfilters/${lookup{$local_part}lsearch{/etc/domainusers}{$value}}"
  47. directory_transport = address_directory
  48. file_transport = address_file
  49. pipe_transport = ${if forall{/bin/cagefs_enter:/usr/sbin/cagefsctl}{exists{$item}}{cagefs_address_pipe}{${if match{${extract{6}{:}{${lookup passwd{$local_part}{$value}}}}}{\N(jail|no)shell\N}{jailed_address_pipe}{address_pipe}}}}
  50. reply_transport = address_reply
  51. retry_use_local_part
  52. no_verify
  53. #
  54. # User Level Filtering for the main account
  55. #
  56. central_user_filter:
  57. driver = redirect
  58. local_part_suffix = +*
  59. local_part_suffix_optional
  60. allow_filter
  61. allow_fail
  62. forbid_filter_run
  63. forbid_filter_perl
  64. forbid_filter_lookup
  65. forbid_filter_readfile
  66. forbid_filter_readsocket
  67. check_local_user
  68. domains = ! +user_domains
  69. require_files = "+${extract{5}{::}{${lookup passwd{$local_part}{$value}}}}/etc/filter"
  70. condition = "${extract{size}{${stat:${extract{5}{:}{${lookup passwd{$local_part}{$value}}}}/etc/filter}}}"
  71. file = "${extract{5}{:}{${lookup passwd{$local_part}{$value}}}}/etc/filter"
  72. router_home_directory = ${extract{5}{:}{${lookup passwd{$local_part}{$value}}}}
  73. directory_transport = address_directory
  74. file_transport = address_file
  75. pipe_transport = ${if forall{/bin/cagefs_enter:/usr/sbin/cagefsctl}{exists{$item}}{cagefs_virtual_address_pipe}{${if match{${extract{6}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}}{\N(jail|no)shell\N}{jailed_virtual_address_pipe}{virtual_address_pipe}}}}
  76. reply_transport = address_reply
  77. retry_use_local_part
  78. no_verify
  79. #
  80. # User Level Filtering for virtual users
  81. #
  82. virtual_user_filter:
  83. driver = redirect
  84. local_part_suffix = +*
  85. local_part_suffix_optional
  86. allow_filter
  87. allow_fail
  88. forbid_filter_run
  89. forbid_filter_perl
  90. forbid_filter_lookup
  91. forbid_filter_readfile
  92. forbid_filter_readsocket
  93. no_check_local_user
  94. domains = +user_domains
  95. require_files = "+${extract{5}{::}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/$local_part/filter"
  96. condition = "${extract{size}{${stat:${extract{5}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/$local_part/filter}}}"
  97. file = "${extract{5}{::}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/$local_part/filter"
  98. router_home_directory = ${extract{5}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}
  99. directory_transport = address_directory
  100. file_transport = address_file
  101. pipe_transport = ${if forall{/bin/cagefs_enter:/usr/sbin/cagefsctl}{exists{$item}}{cagefs_virtual_address_pipe}{${if match{${extract{6}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}}{\N(jail|no)shell\N}{jailed_virtual_address_pipe}{virtual_address_pipe}}}}
  102. reply_transport = address_reply
  103. user = "${lookup{$domain}lsearch* {/etc/userdomains}{$value}}"
  104. retry_use_local_part
  105. no_verify
  106. .ifdef SKIP


  1. .endif
  2. virtual_aliases_nostar:
  3. driver = redirect
  4. local_part_suffix = +*
  5. local_part_suffix_optional
  6. allow_defer
  7. allow_fail
  8. require_files = "+/etc/valiases/$domain"
  9. data = ${lookup{$local_part@$domain}lsearch{/etc/valiases/$domain}}
  10. file_transport = address_file
  11. group = mail
  12. pipe_transport = ${if forall{/bin/cagefs_enter:/usr/sbin/cagefsctl}{exists{$item}}{cagefs_virtual_address_pipe}{${if match{${extract{6}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}}{\N(jail|no)shell\N}{jailed_virtual_address_pipe}{virtual_address_pipe}}}}
  13. retry_use_local_part
  14. unseen
  15. .ifdef SKIP


  1. .endif
  2. #
  3. # Virtual User Spam Boxes
  4. #
  5. virtual_user_spam:
  6. driver = accept
  7. local_part_suffix = +*
  8. local_part_suffix_optional
  9. domains = +user_domains
  10. require_files = "+${extract{5}{::}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/.spamassassinboxenable:+${extract{5}{::}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/passwd"
  11. condition = ${if eq {${lookup {$local_part} lsearch {${extract{5}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/passwd}}}{}{false}{${if match{$h_X-Spam-Status:}{\N^Yes\N}{true}{false}}}}
  12. headers_remove="x-spam-exim"
  13. transport = virtual_userdelivery_spam
  14. virtual_boxtrapper_user:
  15. driver = accept
  16. domains = +user_domains
  17. require_files = "+/usr/local/cpanel/bin/boxtrapper:+${extract{5}{::}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/passwd"
  18. condition = ${if eq {${lookup {$local_part} lsearch {${extract{5}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/passwd}}}{} {false}{${if exists {${extract{5}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/$local_part/.boxtrapperenable} {true} {false}}}}
  19. retry_use_local_part
  20. transport = virtual_boxtrapper_userdelivery
  21. virtual_user:
  22. driver = accept
  23. local_part_suffix = +*
  24. local_part_suffix_optional
  25. headers_remove="x-spam-exim"
  26. domains = +user_domains
  27. require_files = "+${extract{5}{::}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/passwd"
  28. condition = ${if eq {${lookup {$local_part} lsearch {${extract{5}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}/etc/$domain/passwd}}}{} {false}{true}}
  29. transport = virtual_userdelivery
  30. .ifdef SKIP


  1. .endif


  1. valias_domain_file:
  2. driver = redirect
  3. allow_defer
  4. allow_fail
  5. require_files = +/etc/vdomainaliases/$domain
  6. condition = ${lookup {$domain} lsearch {/etc/vdomainaliases/$domain}{yes}{no} }
  7. data = $local_part@${lookup {$domain} lsearch {/etc/vdomainaliases/$domain} }
  8. virtual_aliases:
  9. driver = redirect
  10. local_part_suffix = +*
  11. local_part_suffix_optional
  12. allow_defer
  13. allow_fail
  14. require_files = "+/etc/valiases/$domain"
  15. data = ${lookup{*}lsearch{/etc/valiases/$domain}}
  16. file_transport = address_file
  17. group = mail
  18. pipe_transport = ${if forall{/bin/cagefs_enter:/usr/sbin/cagefsctl}{exists{$item}}{cagefs_virtual_address_pipe}{${if match{${extract{6}{:}{${lookup passwd{${lookup{$domain}lsearch*{/etc/userdomains}{$value}}}{$value}}}}}{\N(jail|no)shell\N}{jailed_virtual_address_pipe}{virtual_address_pipe}}}}
  19. .ifdef SKIP


  1. .endif
  2. # This director handles forwarding using traditional .forward files.
  3. # If you want it also to allow mail filtering when a forward file
  4. # starts with the string "# Exim filter", uncomment the "filter" option.
  5. # The check_ancestor option means that if the forward file generates an
  6. # address that is an ancestor of the current one, the current one gets
  7. # passed on instead. This covers the case where A is aliased to B and B
  8. # has a .forward file pointing to A. The three transports specified at the
  9. # end are those that are used when forwarding generates a direct delivery
  10. # to a file, or to a pipe, or sets up an auto-reply, respectively.
  11. system_aliases:
  12. driver = redirect
  13. local_part_suffix = +*
  14. local_part_suffix_optional
  15. allow_defer
  16. allow_fail
  17. data = ${lookup{$local_part}lsearch{/etc/aliases}}
  18. file_transport = address_file
  19. pipe_transport = address_pipe
  20. retry_use_local_part
  21. # user = exim
  22. local_aliases:
  23. driver = redirect
  24. local_part_suffix = +*
  25. local_part_suffix_optional
  26. allow_defer
  27. allow_fail
  28. data = ${lookup{$local_part}lsearch{/etc/localaliases}}
  29. file_transport = address_file
  30. pipe_transport = address_pipe
  31. check_local_user
  32. .ifdef SKIP


  1. .endif
  2. userforward:
  3. driver = redirect
  4. local_part_suffix = +*
  5. local_part_suffix_optional
  6. allow_filter
  7. allow_fail
  8. forbid_filter_run
  9. forbid_filter_perl
  10. forbid_filter_lookup
  11. forbid_filter_readfile
  12. forbid_filter_readsocket
  13. check_ancestor
  14. check_local_user
  15. domains = ! +user_domains
  16. no_expn
  17. require_files = "+$home/.forward"
  18. condition = "${extract{size}{${stat:$home/.forward}}}"
  19. file = $home/.forward
  20. file_transport = address_file
  21. pipe_transport = ${if forall{/bin/cagefs_enter:/usr/sbin/cagefsctl}{exists{$item}}{cagefs_address_pipe}{${if match{${extract{6}{:}{${lookup passwd{$local_part}{$value}}}}}{\N(jail|no)shell\N}{jailed_address_pipe}{address_pipe}}}}
  22. reply_transport = address_reply
  23. directory_transport = address_directory
  24. no_verify
  25. .ifdef SKIP


  1. .endif


There is a significant need for this function. Any chance it can make it into a release.


Yeah WHM/cPanel team, please make this happen.


When applying the work-around posted by Nic Jones, be aware that WHM's Exim Configuration Manager advanced editor complains about multiple errors in the work-around, but save it anyway.

After I applied the work-around exim allows the "plus addressed" messages to get through, but it also bounces a "Mail delivery failed: returning message to sender" message back to the sender.

cPanel Team, you really should come up with a better solution for this. As far as I'm concerned, without a working configuration option to make this work easily, cPanel's mail system is "broken". If it stays broken, I'll have to move my servers away from cPanel.


Hello! We are having the same needs. Our Swedish provider is using cPanel and we have to look for alternatives in case we cannot get this feature any time soon (i.e. first two quarters 2015). Thank you in advance!




...this works fine, when intelligent people make websites, however -- & far too often -- some websites are written by someone that doesn't know that + (plus) is a VALID char in an E-mail address, so that website rejects "" as being an invalid E-mail address, even when it is perfectly valid.

I think you are mistaking smart guys with stupid guys.

Unintelligent people would have no validation on their web forms. Those forms would accept the "+" symbol even when their email server does NOT support it, and then would deal with tickets and bug reports all day because of it. Then they would complain about it on forums and wherever they could.

The smart ones would never allow their sites to collect addresses that cannot be served. Then they'd make a feature request like this. And then, once it was deployed on the server-side of life, they could easily change the validation on their site to accept the "+"

Cngratulations on making the first step.


This feature is absolutely necessary nowadays. Too many people are used to this functionality in gmail and try to use it on my server. I can sign them up for a google apps account and charge them more I suppose, but i don't want to do that...


Copy of my email to support:

You know what is funny? Funny is that you're using subaddressing in your own ticket system (like i'm getting emails with sender address like - 6722469+***HIDDEN*** But you're still wondering if your customers need that feature? Are this for real? :)

Correct me if i'm wrong - that means that your ticket system hosted not on the WHM/Cpanel servers? Otherwise you cannot use "plus-emails" in your ticket system because CPanel doesn't support it.

If I'm right - do you think you still need to ask your customers if this feature should be implemented in CPanel?! It should! Asap.


They forward all e-mails to their ticketing system script. You can already do that in cPanel.

It is true tough that for webmail and imap users this could be nice.


Mailman is able to use plus addressing in Cpanel. Can't be that hard for Cpanel to implement.


Would it be possible to use "default address" and a perl or php script to catch the mails and redirect them to the correct address?


This feature has been standard on GMail for over a decade. It should not require a "hack" or external script, it should be a core feature. I'll be seeking alternatives to cPanel based hosting for my domains until plus addressing is implemented. Thanks.


  • Workaround no longer needed, as this is live in v58
  • This information is shared privately in spoiler and not visible!

Hope it helps somebody.


This functionality will be included in v58 and is available in EDGE


cPanel & WHM v58 has just gone to CURRENT and includes this functionality. If you have any questions let me know, or if you experience any problems with it, please do submit a ticket and let us know!

Replies have been locked on this page!