Date: Sun, 10 May 2020 16:27:27 +0200
Subject: [PATCH 10/33] optimize import email_handler
---
email_handler.py | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/email_handler.py b/email_handler.py
index 6c76b4db..603498a3 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -31,14 +31,8 @@ It should contain the following info:
"""
import email
-import re
-
-import arrow
-import spf
import time
import uuid
-from aiosmtpd.controller import Controller
-from aiosmtpd.smtp import Envelope
from email import encoders
from email.message import Message
from email.mime.application import MIMEApplication
@@ -47,6 +41,11 @@ from email.utils import parseaddr, formataddr
from io import BytesIO
from smtplib import SMTP
+import arrow
+import spf
+from aiosmtpd.controller import Controller
+from aiosmtpd.smtp import Envelope
+
from app import pgp_utils, s3
from app.alias_utils import try_auto_create
from app.config import (
From b5e7f05bfcd10eee054845eaa5d4d2da8e1c1701 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 16:28:56 +0200
Subject: [PATCH 11/33] allow user sends emails to his alias from his mailbox
---
email_handler.py | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/email_handler.py b/email_handler.py
index 603498a3..c8431b63 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -93,9 +93,6 @@ from app.utils import random_string
from init_app import load_pgp_public_keys
from server import create_app
-# used when an alias receives email from its own mailbox
-# can happen when user "Reply All" on some email clients
-_SELF_FORWARDING_STATUS = "550 SL self-forward"
_IP_HEADER = "X-SimpleLogin-Client-IP"
@@ -331,13 +328,6 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
mailbox_email = mailbox.email
user = alias.user
- # Sometimes when user clicks on "reply all"
- # an email is sent to the same alias that the previous message is destined to
- if envelope.mail_from == mailbox_email:
- # nothing to do
- LOG.d("Forward from %s to %s, nothing to do", envelope.mail_from, mailbox_email)
- return False, _SELF_FORWARDING_STATUS
-
contact = get_or_create_contact(msg["From"], alias)
email_log = EmailLog.create(contact_id=contact.id, user_id=contact.user_id)
@@ -940,12 +930,6 @@ def handle(envelope: Envelope, smtp: SMTP) -> str:
is_delivered, smtp_status = handle_forward(envelope, smtp, msg, rcpt_to)
res.append((is_delivered, smtp_status))
- # special handling for self-forwarding
- # just consider success delivery in this case
- if len(res) == 1 and res[0][1] == _SELF_FORWARDING_STATUS:
- LOG.d("Self-forwarding, ignore")
- return "250 SL OK"
-
for (is_success, smtp_status) in res:
# Consider all deliveries successful if 1 delivery is successful
if is_success:
From 59036972f1e046e6f912d8f9e3c1c43f44563ae8 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 16:31:36 +0200
Subject: [PATCH 12/33] refactor handle_forward: move the disabled alias case
to the beginning
---
email_handler.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/email_handler.py b/email_handler.py
index c8431b63..975aa905 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -324,10 +324,6 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
LOG.d("alias %s cannot be created on-the-fly, return 550", address)
return False, "550 SL E3"
- mailbox = alias.mailbox
- mailbox_email = mailbox.email
- user = alias.user
-
contact = get_or_create_contact(msg["From"], alias)
email_log = EmailLog.create(contact_id=contact.id, user_id=contact.user_id)
@@ -336,8 +332,13 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
email_log.blocked = True
db.session.commit()
+ # do not return 5** to allow user to receive emails later when alias is enabled
return True, "250 Message accepted for delivery"
+ mailbox = alias.mailbox
+ mailbox_email = mailbox.email
+ user = alias.user
+
spam_check = True
# create PGP email if needed
From 2755e67c31dfd4faeec2a7e42787ac3c5effb982 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 16:32:54 +0200
Subject: [PATCH 13/33] simplify code: replace mailbox_email by mailbox.email
---
email_handler.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/email_handler.py b/email_handler.py
index 975aa905..44157c67 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -336,7 +336,6 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
return True, "250 Message accepted for delivery"
mailbox = alias.mailbox
- mailbox_email = mailbox.email
user = alias.user
spam_check = True
@@ -356,7 +355,7 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
email_log.is_spam = True
email_log.spam_status = spam_status
- handle_spam(contact, alias, msg, user, mailbox_email, email_log)
+ handle_spam(contact, alias, msg, user, mailbox.email, email_log)
return False, "550 SL E1"
# add custom header
@@ -406,7 +405,7 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
LOG.d(
"Forward mail from %s to %s, mail_options %s, rcpt_options %s ",
contact.website_email,
- mailbox_email,
+ mailbox.email,
envelope.mail_options,
envelope.rcpt_options,
)
@@ -415,7 +414,7 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
# encode message raw directly instead
smtp.sendmail(
contact.reply_email,
- mailbox_email,
+ mailbox.email,
msg.as_bytes(),
envelope.mail_options,
envelope.rcpt_options,
From 7f6ba313fd94198629ccd2bb5a315eb6533b79c6 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 16:33:54 +0200
Subject: [PATCH 14/33] add strip() to rcpt_to just in case
---
email_handler.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/email_handler.py b/email_handler.py
index 44157c67..f3fee89d 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -314,7 +314,7 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
"""return whether an email has been delivered and
the smtp status ("250 Message accepted", "550 Non-existent email address", etc)
"""
- address = rcpt_to.lower() # alias@SL
+ address = rcpt_to.lower().strip() # alias@SL
alias = Alias.get_by(email=address)
if not alias:
From 4b479defa895e21d672e53bb4396273f63f9e0d9 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 16:57:47 +0200
Subject: [PATCH 15/33] Support alias having multiple mailboxes in forward
phase
---
app/models.py | 8 ++++++++
email_handler.py | 38 ++++++++++++++++++++++++++++++++------
2 files changed, 40 insertions(+), 6 deletions(-)
diff --git a/app/models.py b/app/models.py
index e18a0066..22e78a85 100644
--- a/app/models.py
+++ b/app/models.py
@@ -643,6 +643,14 @@ class Alias(db.Model, ModelMixin):
user = db.relationship(User)
mailbox = db.relationship("Mailbox")
+ @property
+ def mailboxes(self):
+ ret = [self.mailbox]
+ for m in self._mailboxes:
+ ret.append(m)
+
+ return ret
+
@classmethod
def create(cls, **kw):
r = cls(**kw)
diff --git a/email_handler.py b/email_handler.py
index f3fee89d..ad1af564 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -40,6 +40,7 @@ from email.mime.multipart import MIMEMultipart
from email.utils import parseaddr, formataddr
from io import BytesIO
from smtplib import SMTP
+from typing import List, Tuple
import arrow
import spf
@@ -310,7 +311,9 @@ def prepare_pgp_message(orig_msg: Message, pgp_fingerprint: str):
return msg
-def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, str):
+def handle_forward(
+ envelope, smtp: SMTP, msg: Message, rcpt_to: str
+) -> List[Tuple[bool, str]]:
"""return whether an email has been delivered and
the smtp status ("250 Message accepted", "550 Non-existent email address", etc)
"""
@@ -322,7 +325,7 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
alias = try_auto_create(address)
if not alias:
LOG.d("alias %s cannot be created on-the-fly, return 550", address)
- return False, "550 SL E3"
+ return [(False, "550 SL E3")]
contact = get_or_create_contact(msg["From"], alias)
email_log = EmailLog.create(contact_id=contact.id, user_id=contact.user_id)
@@ -333,11 +336,32 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
db.session.commit()
# do not return 5** to allow user to receive emails later when alias is enabled
- return True, "250 Message accepted for delivery"
+ return [(True, "250 Message accepted for delivery")]
- mailbox = alias.mailbox
user = alias.user
+ ret = []
+ for mailbox in alias.mailboxes:
+ ret.append(
+ forward_email_to_mailbox(
+ alias, msg, email_log, contact, envelope, smtp, mailbox, user
+ )
+ )
+
+ return ret
+
+
+def forward_email_to_mailbox(
+ alias,
+ msg: Message,
+ email_log: EmailLog,
+ contact: Contact,
+ envelope,
+ smtp: SMTP,
+ mailbox,
+ user,
+) -> (bool, str):
+ LOG.d("Forward %s -> %s -> %s", contact, alias, mailbox)
spam_check = True
# create PGP email if needed
@@ -927,8 +951,10 @@ def handle(envelope: Envelope, smtp: SMTP) -> str:
res.append((is_delivered, smtp_status))
else: # Forward case
LOG.debug(">>> Forward phase %s -> %s", envelope.mail_from, rcpt_to)
- is_delivered, smtp_status = handle_forward(envelope, smtp, msg, rcpt_to)
- res.append((is_delivered, smtp_status))
+ for is_delivered, smtp_status in handle_forward(
+ envelope, smtp, msg, rcpt_to
+ ):
+ res.append((is_delivered, smtp_status))
for (is_success, smtp_status) in res:
# Consider all deliveries successful if 1 delivery is successful
From 97e1c334aff6945a771fc6548f3b99767fe41512 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 16:58:18 +0200
Subject: [PATCH 16/33] call strip() on rcpt_to just to be sure
---
email_handler.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/email_handler.py b/email_handler.py
index ad1af564..20921bba 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -453,7 +453,7 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, str
return whether an email has been delivered and
the smtp status ("250 Message accepted", "550 Non-existent email address", etc)
"""
- reply_email = rcpt_to.lower()
+ reply_email = rcpt_to.lower().strip()
# reply_email must end with EMAIL_DOMAIN
if not reply_email.endswith(EMAIL_DOMAIN):
From 8d65175ac59e6c9eee7a41f9e8efe64b4af620c8 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 17:52:07 +0200
Subject: [PATCH 17/33] set mailbox ID in X-SimpleLogin-Mailbox-ID header
---
email_handler.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/email_handler.py b/email_handler.py
index 20921bba..a826948b 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -96,7 +96,7 @@ from server import create_app
_IP_HEADER = "X-SimpleLogin-Client-IP"
-
+_MAILBOX_ID_HEADER = "X-SimpleLogin-Mailbox-ID"
# fix the database connection leak issue
# use this method instead of create_app
@@ -390,6 +390,7 @@ def forward_email_to_mailbox(
delete_header(msg, "Sender")
delete_header(msg, _IP_HEADER)
+ add_or_replace_header(msg, _MAILBOX_ID_HEADER, str(mailbox.id))
# change the from header so the sender comes from @SL
# so it can pass DMARC check
From 33d578c78e293941706be892dd1723abadd68e7f Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 17:53:37 +0200
Subject: [PATCH 18/33] parse _MAILBOX_ID_HEADER to handle bounce message
---
email_handler.py | 40 +++++++++++++++++++++++++++-------------
1 file changed, 27 insertions(+), 13 deletions(-)
diff --git a/email_handler.py b/email_handler.py
index a826948b..469e5675 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -490,7 +490,7 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, str
alias.user,
)
- handle_bounce(contact, alias, msg, user, mailbox_email)
+ handle_bounce(contact, alias, msg, user)
return False, "550 SL E6"
mailbox: Mailbox = Mailbox.get_by(email=mailbox_email)
@@ -688,9 +688,7 @@ def handle_unknown_mailbox(
)
-def handle_bounce(
- contact: Contact, alias: Alias, msg: Message, user: User, mailbox_email: str
-):
+def handle_bounce(contact: Contact, alias: Alias, msg: Message, user: User):
address = alias.email
email_log: EmailLog = EmailLog.create(
contact_id=contact.id, bounced=True, user_id=contact.user_id
@@ -708,12 +706,28 @@ def handle_bounce(
full_report_path = f"refused-emails/full-{random_name}.eml"
s3.upload_email_from_bytesio(full_report_path, BytesIO(msg.as_bytes()), random_name)
- file_path = None
- if orig_msg:
- file_path = f"refused-emails/{random_name}.eml"
- s3.upload_email_from_bytesio(
- file_path, BytesIO(orig_msg.as_bytes()), random_name
+ if not orig_msg:
+ LOG.error(
+ "Cannot parse original message from bounce message %s %s %s",
+ alias,
+ user,
+ contact,
)
+ return
+
+ file_path = f"refused-emails/{random_name}.eml"
+ s3.upload_email_from_bytesio(file_path, BytesIO(orig_msg.as_bytes()), random_name)
+ mailbox_id = int(orig_msg[_MAILBOX_ID_HEADER])
+ mailbox = Mailbox.get(mailbox_id)
+ if not mailbox or mailbox.user_id != user.id:
+ LOG.error(
+ "Tampered message mailbox_id %s, %s, %s, %s",
+ mailbox_id,
+ user,
+ alias,
+ contact,
+ )
+ return
refused_email = RefusedEmail.create(
path=file_path, full_report_path=full_report_path, user_id=user.id
@@ -750,7 +764,7 @@ def handle_bounce(
website_email=contact.website_email,
disable_alias_link=disable_alias_link,
refused_email_url=refused_email_url,
- mailbox_email=mailbox_email,
+ mailbox_email=mailbox.email,
),
render(
"transactional/bounced-email.html",
@@ -759,7 +773,7 @@ def handle_bounce(
website_email=contact.website_email,
disable_alias_link=disable_alias_link,
refused_email_url=refused_email_url,
- mailbox_email=mailbox_email,
+ mailbox_email=mailbox.email,
),
# cannot include bounce email as it can contain spammy text
# bounced_email=msg,
@@ -786,7 +800,7 @@ def handle_bounce(
alias=alias,
website_email=contact.website_email,
refused_email_url=refused_email_url,
- mailbox_email=mailbox_email,
+ mailbox_email=mailbox.email,
),
render(
"transactional/automatic-disable-alias.html",
@@ -794,7 +808,7 @@ def handle_bounce(
alias=alias,
website_email=contact.website_email,
refused_email_url=refused_email_url,
- mailbox_email=mailbox_email,
+ mailbox_email=mailbox.email,
),
# cannot include bounce email as it can contain spammy text
# bounced_email=msg,
From 336bdb196d200ef794f86a16500109ed0f724f72 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 18:19:29 +0200
Subject: [PATCH 19/33] Detect unknown mailbox using envelope mail_from
---
email_handler.py | 39 ++++++++-----------
.../reply-must-use-personal-email.html | 25 ++++++++++--
.../reply-must-use-personal-email.txt | 11 +++++-
3 files changed, 46 insertions(+), 29 deletions(-)
diff --git a/email_handler.py b/email_handler.py
index 469e5675..6673f0d8 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -476,24 +476,26 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, str
return False, "550 SL E5"
user = alias.user
- mailbox_email = alias.mailbox_email()
+ mail_from = envelope.mail_from.lower().strip()
# bounce email initiated by Postfix
# can happen in case emails cannot be delivered to user-email
# in this case Postfix will try to send a bounce report to original sender, which is
# the "reply email"
- if envelope.mail_from == "<>":
+ if mail_from == "<>":
LOG.warning(
- "Bounce when sending to alias %s from %s, user %s",
- alias,
- contact.website_email,
- alias.user,
+ "Bounce when sending to alias %s from %s, user %s", alias, contact, user,
)
handle_bounce(contact, alias, msg, user)
return False, "550 SL E6"
- mailbox: Mailbox = Mailbox.get_by(email=mailbox_email)
+ mailbox = Mailbox.get_by(email=mail_from, user_id=user.id)
+ if not mailbox or mailbox not in alias.mailboxes:
+ # only mailbox can send email to the reply-email
+ handle_unknown_mailbox(envelope, msg, reply_email, user, alias)
+ return False, "550 SL E7"
+
if ENFORCE_SPF and mailbox.force_spf:
ip = msg[_IP_HEADER]
if not spf_pass(ip, envelope, mailbox, user, alias, contact.website_email, msg):
@@ -501,13 +503,7 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, str
delete_header(msg, _IP_HEADER)
- # only mailbox can send email to the reply-email
- if envelope.mail_from.lower() != mailbox_email.lower():
- handle_unknown_mailbox(envelope, msg, mailbox, reply_email, user, alias)
- return False, "550 SL E7"
-
delete_header(msg, "DKIM-Signature")
-
delete_header(msg, "Received")
# make the email comes from alias
@@ -636,36 +632,33 @@ def spf_pass(
return True
-def handle_unknown_mailbox(
- envelope, msg, mailbox: Mailbox, reply_email: str, user: User, alias: Alias
-):
+def handle_unknown_mailbox(envelope, msg, reply_email: str, user: User, alias: Alias):
LOG.warning(
f"Reply email can only be used by mailbox. "
- f"Actual mail_from: %s. msg from header: %s, Mailbox %s. reply_email %s",
+ f"Actual mail_from: %s. msg from header: %s, reverse-alias %s, %s %s",
envelope.mail_from,
msg["From"],
- mailbox.email,
reply_email,
+ alias,
+ user,
)
send_email_with_rate_control(
user,
ALERT_REVERSE_ALIAS_UNKNOWN_MAILBOX,
- mailbox.email,
+ user.email,
f"Reply from your alias {alias.email} only works from your mailbox",
render(
"transactional/reply-must-use-personal-email.txt",
name=user.name,
- alias=alias.email,
+ alias=alias,
sender=envelope.mail_from,
- mailbox_email=mailbox.email,
),
render(
"transactional/reply-must-use-personal-email.html",
name=user.name,
- alias=alias.email,
+ alias=alias,
sender=envelope.mail_from,
- mailbox_email=mailbox.email,
),
)
diff --git a/templates/emails/transactional/reply-must-use-personal-email.html b/templates/emails/transactional/reply-must-use-personal-email.html
index 790782ad..356aa1aa 100644
--- a/templates/emails/transactional/reply-must-use-personal-email.html
+++ b/templates/emails/transactional/reply-must-use-personal-email.html
@@ -2,10 +2,27 @@
{% block content %}
{{ render_text("Hi " + name) }}
- {{ render_text("We have recorded an attempt to send an email from your alias
"+ alias +" using
" + sender + ".") }}
- {{ render_text("Please note that sending from this alias only works from
" + mailbox_email + ".") }}
- {{ render_text("Indeed, only you (or the mailbox that owns
" + alias + ") can send emails on behalf of this alias.") }}
- {{ render_text('Thanks,
SimpleLogin Team.') }}
+
+ {% call text() %}
+ We have recorded an attempt to send an email from your alias
{{ alias.email }} using
{{ sender }}>
+ {% endcall %}
+
+ {% call text() %}
+ Please note that sending from this alias only works from one of these mailboxes:
+ {% for mailbox in alias.mailboxes %}
+ - {{ mailbox.email }}
+ {% endfor %}
+ {% endcall %}
+
+ {% call text() %}
+ Indeed only you can send emails on behalf of your alias.
+ {% endcall %}
+
+ {% call text() %}
+ Thanks,
+ SimpleLogin Team.
+ {% endcall %}
+
{% endblock %}
diff --git a/templates/emails/transactional/reply-must-use-personal-email.txt b/templates/emails/transactional/reply-must-use-personal-email.txt
index 1ee52f1f..21e4b7fe 100644
--- a/templates/emails/transactional/reply-must-use-personal-email.txt
+++ b/templates/emails/transactional/reply-must-use-personal-email.txt
@@ -1,8 +1,15 @@
Hi {{name}}
-We have recorded an attempt to send an email from your alias {{ alias }} using {{ sender }}.
+We have recorded an attempt to send an email from your alias {{ alias.email }} using {{ sender }}.
-Please note that sending from this alias only works from {{mailbox_email}}: only you (i.e. no one else) can send emails on behalf of your alias.
+Please note that sending from this alias only works from one of these mailboxes:
+
+{% for mailbox in alias.mailboxes %}
+- {{mailbox.email}}
+{% endfor %}
+
+
+Indeed only you can send emails on behalf of your alias.
Best,
SimpleLogin team.
From 5b71b34f9e62c07eefc01ff450b6db7413bcb0e4 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 18:23:43 +0200
Subject: [PATCH 20/33] handle alias unsubscribe
---
email_handler.py | 37 ++++++++++++++++++++-----------------
1 file changed, 20 insertions(+), 17 deletions(-)
diff --git a/email_handler.py b/email_handler.py
index 6673f0d8..50f4d36b 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -900,7 +900,9 @@ def handle_unsubscribe(envelope: Envelope):
return "550 SL E9"
# This sender cannot unsubscribe
- if alias.mailbox_email() != envelope.mail_from:
+ mail_from = envelope.mail_from.lower().strip()
+ mailbox = Mailbox.get_by(user_id=alias.user_id, email=mail_from)
+ if not mailbox or mailbox not in alias.mailboxes:
LOG.d("%s cannot disable alias %s", envelope.mail_from, alias)
return "550 SL E10"
@@ -910,22 +912,23 @@ def handle_unsubscribe(envelope: Envelope):
user = alias.user
enable_alias_url = URL + f"/dashboard/?highlight_alias_id={alias.id}"
- send_email(
- envelope.mail_from,
- f"Alias {alias.email} has been disabled successfully",
- render(
- "transactional/unsubscribe-disable-alias.txt",
- user=user,
- alias=alias.email,
- enable_alias_url=enable_alias_url,
- ),
- render(
- "transactional/unsubscribe-disable-alias.html",
- user=user,
- alias=alias.email,
- enable_alias_url=enable_alias_url,
- ),
- )
+ for mailbox in alias.mailboxes:
+ send_email(
+ mailbox.email,
+ f"Alias {alias.email} has been disabled successfully",
+ render(
+ "transactional/unsubscribe-disable-alias.txt",
+ user=user,
+ alias=alias.email,
+ enable_alias_url=enable_alias_url,
+ ),
+ render(
+ "transactional/unsubscribe-disable-alias.html",
+ user=user,
+ alias=alias.email,
+ enable_alias_url=enable_alias_url,
+ ),
+ )
return "250 Unsubscribe request accepted"
From 0f09ef681ca00b1060dd733188adeb76e5f0b455 Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 18:34:57 +0200
Subject: [PATCH 21/33] Add EmailLog.bounced_mailbox_id
---
app/models.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/app/models.py b/app/models.py
index 22e78a85..44cc4618 100644
--- a/app/models.py
+++ b/app/models.py
@@ -923,6 +923,12 @@ class EmailLog(db.Model, ModelMixin):
db.ForeignKey("refused_email.id", ondelete="SET NULL"), nullable=True
)
+ # in case of bounce, record on what mailbox the email has been bounced
+ # useful when an alias has several mailboxes
+ bounced_mailbox_id = db.Column(
+ db.ForeignKey("mailbox.id", ondelete="cascade"), nullable=True
+ )
+
refused_email = db.relationship("RefusedEmail")
forward = db.relationship(Contact)
From 0d117126db4ea3dcedcf65cc078a8c42fd79438b Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 18:35:13 +0200
Subject: [PATCH 22/33] save the mailbox that a bounce affects
---
email_handler.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/email_handler.py b/email_handler.py
index 50f4d36b..ffa819f3 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -728,6 +728,7 @@ def handle_bounce(contact: Contact, alias: Alias, msg: Message, user: User):
db.session.flush()
email_log.refused_email_id = refused_email.id
+ email_log.bounced_mailbox_id = mailbox.id
db.session.commit()
LOG.d("Create refused email %s", refused_email)
From bc55b98e12d731df9780a0f0bc91a91979ac4d8f Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 10 May 2020 18:41:22 +0200
Subject: [PATCH 23/33] display mailbox that a bounce affects
---
app/dashboard/templates/dashboard/alias_log.html | 2 +-
app/dashboard/views/alias_log.py | 5 ++---
app/models.py | 6 ++++++
3 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/app/dashboard/templates/dashboard/alias_log.html b/app/dashboard/templates/dashboard/alias_log.html
index 66cc21d3..40a3b6bb 100644
--- a/app/dashboard/templates/dashboard/alias_log.html
+++ b/app/dashboard/templates/dashboard/alias_log.html
@@ -129,7 +129,7 @@
{{ log.alias }}
-
{{ log.mailbox }}
+
{{ log.email_log.bounced_mailbox() }}