Repository: modoboa/modoboa

Issue:
Create alias with tag (+) in recipient address with internal domain
It's impossible to create new alias with tag in recipient address.

Example : 
- I've contact@example.com mailbox
- I would like to create foobar@example.com alias with contact+test@example.com recipient

I've this error « Local recipient contact+test@example.com not found ».

Solution proposition : 
- use Alias.extmboxes to record this email address with tag

Do you see other solution ?



Discussion:
This fix could indeed work but it requires a parsing of the email address to detect the delimiter (+) :
- Parse the address
- If there a delimiter character in the local part, remove everything until we found a @ character
- With the remaining address (so contact@example.com) in the previous example, check if it exists locally
- Create the alias using the extmboxes field

Is it corresponding to your intial idea ?

Ok, I'm going to implement that.

Great news :)

By the way, I'm currently splitting modoboa applications (I'm almost done with 1.3.0) so the modoboa repository now contains only core files.

Since I consider your need as a feature, I think you should implement it inside the new repository which contains the admin console code: https://github.com/modoboa/modoboa-admin

What do you think ?

First, I'm going to fix it in stable version 1.2.1, next I'll implement it to 1.3.0.

Ok, so the 1.2.x branch is the one you're looking for ;)

Anyway, many thanks for you time!


Resolving patch:
diff --git a/modoboa/extensions/admin/forms/alias.py b/modoboa/extensions/admin/forms/alias.py
--- a/modoboa/extensions/admin/forms/alias.py
+++ b/modoboa/extensions/admin/forms/alias.py
@@ -96,31 +96,36 @@ def set_recipients(self):
                 raise BadRequest(
                     u"%s %s" % (_("Invalid mailbox"), v)
                 )
-            try:
-                domain = Domain.objects.get(name=domname)
-            except Domain.DoesNotExist:
-                domain = None
+
+            # Support tag in recipient, see https://github.com/tonioo/modoboa/issues/713
+            local_part_with_tag = None
+            if '+' in local_part:
+                local_part_with_tag = local_part
+                local_part = local_part[0:local_part.find('+')]
+
+            domain = Domain.objects.filter(name=domname).first()
+
             if domain is not None:
-                try:
-                    rcpt = Alias.objects.get(domain=domain, address=local_part)
-                    if rcpt.full_address == self.cleaned_data["email"]:
-                        rcpt = None
-                except Alias.DoesNotExist:
+                rcpt = Alias.objects.filter(domain=domain, address=local_part).first()
+                if rcpt and (rcpt.full_address == self.cleaned_data["email"]):
                     rcpt = None
+
                 if rcpt is None:
                     try:
                         rcpt = Mailbox.objects.get(domain=domain, address=local_part)
                     except Mailbox.DoesNotExist:
                         raise NotFound(
-                            _("Local recipient %s not found" % v)
+                            _("Local recipient %s@%s not found" % (local_part, domname))
                         )
-                if rcpt in self.int_rcpts:
-                    raise Conflict(
-                        _("Recipient %s already present" % v)
-                    )
-                self.int_rcpts += [rcpt]
-                total += 1
-                continue
+
+                if local_part_with_tag is None:
+                    if rcpt in self.int_rcpts:
+                        raise Conflict(
+                            _("Recipient %s already present" % v)
+                        )
+                    self.int_rcpts += [rcpt]
+                    total += 1
+                    continue
 
             if v in self.ext_rcpts:
                 raise Conflict(
@@ -134,8 +139,8 @@ def set_recipients(self):
 
     def save(self, commit=True):
         alias = super(AliasForm, self).save(commit=False)
-        localpart, domname = split_mailbox(self.cleaned_data["email"])
-        alias.address = localpart
+        local_part, domname = split_mailbox(self.cleaned_data["email"])
+        alias.address = local_part
         alias.domain = Domain.objects.get(name=domname)
         if commit:
             alias.save(int_rcpts=self.int_rcpts, ext_rcpts=self.ext_rcpts)


Test patch:
diff --git a/modoboa/extensions/admin/tests/alias.py b/modoboa/extensions/admin/tests/alias.py
--- a/modoboa/extensions/admin/tests/alias.py
+++ b/modoboa/extensions/admin/tests/alias.py
@@ -62,6 +62,26 @@ def test_upper_case_alias(self):
             "titi@test.com"
         )
 
+    def test_append_alias_with_tag(self):
+        """Try to create a alias with tag in recipient address"""
+        user = User.objects.get(username="user@test.com")
+        values = dict(
+            username="user@test.com", role=user.group,
+            is_active=user.is_active, email="user@test.com"
+        )
+        self.ajax_post(
+            reverse("admin:account_change", args=[user.id]),
+            values
+        )
+
+        values = {
+            "email": "foobar@test.com", "recipients": "user+spam@test.com",
+            "enabled": True
+        }
+        self.ajax_post(reverse("admin:alias_add"), values)
+        alias = Alias.objects.get(address="foobar", domain__name="test.com")
+        self.assertEqual(alias.extmboxes, "user+spam@test.com")
+
     def test_dlist(self):
         values = dict(email="all@test.com",
                       recipients="user@test.com",