瀏覽代碼

Store the bounced email in email handling.

Son NK 5 年之前
父節點
當前提交
0bb9830680

+ 15 - 0
app/email_utils.py

@@ -363,3 +363,18 @@ def mailbox_already_used(email: str, user) -> bool:
         return True
         return True
 
 
     return False
     return False
+
+
+def get_orig_message_from_bounce(msg: Message) -> Message:
+    """parse the original email from Bounce"""
+    i = 0
+    for part in msg.walk():
+        i += 1
+
+        # the original message is the 4th part
+        # 1st part is the root part,  multipart/report
+        # 2nd is text/plain, Postfix log
+        # ...
+        # 7th is original message
+        if i == 7:
+            return part

+ 44 - 9
email_handler.py

@@ -37,21 +37,19 @@ from email.mime.application import MIMEApplication
 from email.mime.multipart import MIMEMultipart
 from email.mime.multipart import MIMEMultipart
 from email.parser import Parser
 from email.parser import Parser
 from email.policy import SMTPUTF8
 from email.policy import SMTPUTF8
+from io import BytesIO
 from smtplib import SMTP
 from smtplib import SMTP
 from typing import Optional
 from typing import Optional
 
 
 from aiosmtpd.controller import Controller
 from aiosmtpd.controller import Controller
-import gnupg
 
 
+from app import pgp_utils, s3
 from app.config import (
 from app.config import (
     EMAIL_DOMAIN,
     EMAIL_DOMAIN,
     POSTFIX_SERVER,
     POSTFIX_SERVER,
     URL,
     URL,
     ALIAS_DOMAINS,
     ALIAS_DOMAINS,
-    ADMIN_EMAIL,
-    SUPPORT_EMAIL,
     POSTFIX_SUBMISSION_TLS,
     POSTFIX_SUBMISSION_TLS,
-    GNUPGHOME,
 )
 )
 from app.email_utils import (
 from app.email_utils import (
     get_email_name,
     get_email_name,
@@ -65,6 +63,7 @@ from app.email_utils import (
     send_cannot_create_domain_alias,
     send_cannot_create_domain_alias,
     email_belongs_to_alias_domains,
     email_belongs_to_alias_domains,
     render,
     render,
+    get_orig_message_from_bounce,
 )
 )
 from app.extensions import db
 from app.extensions import db
 from app.log import LOG
 from app.log import LOG
@@ -76,10 +75,10 @@ from app.models import (
     Directory,
     Directory,
     User,
     User,
     DeletedAlias,
     DeletedAlias,
+    RefusedEmail,
 )
 )
 from app.utils import random_string
 from app.utils import random_string
 from server import create_app
 from server import create_app
-from app import pgp_utils
 
 
 
 
 # fix the database connection leak issue
 # fix the database connection leak issue
@@ -406,7 +405,12 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
     # in this case Postfix will try to send a bounce report to original sender, which is
     # in this case Postfix will try to send a bounce report to original sender, which is
     # the "reply email"
     # the "reply email"
     if envelope.mail_from == "<>":
     if envelope.mail_from == "<>":
-        LOG.error("Bounce when sending to alias %s, user %s", alias, gen_email.user)
+        LOG.error(
+            "Bounce when sending to alias %s from %s, user %s",
+            alias,
+            forward_email.website_from,
+            gen_email.user,
+        )
 
 
         handle_bounce(
         handle_bounce(
             alias, envelope, forward_email, gen_email, msg, smtp, user, mailbox_email
             alias, envelope, forward_email, gen_email, msg, smtp, user, mailbox_email
@@ -513,7 +517,9 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> str:
 def handle_bounce(
 def handle_bounce(
     alias, envelope, forward_email, gen_email, msg, smtp, user, mailbox_email
     alias, envelope, forward_email, gen_email, msg, smtp, user, mailbox_email
 ):
 ):
-    ForwardEmailLog.create(forward_id=forward_email.id, bounced=True)
+    fel: ForwardEmailLog = ForwardEmailLog.create(
+        forward_id=forward_email.id, bounced=True
+    )
     db.session.commit()
     db.session.commit()
 
 
     nb_bounced = ForwardEmailLog.filter_by(
     nb_bounced = ForwardEmailLog.filter_by(
@@ -521,6 +527,30 @@ def handle_bounce(
     ).count()
     ).count()
     disable_alias_link = f"{URL}/dashboard/unsubscribe/{gen_email.id}"
     disable_alias_link = f"{URL}/dashboard/unsubscribe/{gen_email.id}"
 
 
+    # Store the bounced email
+    random_name = random_string(50)
+
+    full_report_path = f"refused-emails/full-{random_name}.eml"
+    s3.upload_from_bytesio(full_report_path, BytesIO(msg.as_bytes()))
+
+    file_path = f"refused-emails/{random_name}.eml"
+    orig_msg = get_orig_message_from_bounce(msg)
+    s3.upload_from_bytesio(file_path, BytesIO(orig_msg.as_bytes()))
+
+    refused_email = RefusedEmail.create(
+        path=file_path, full_report_path=full_report_path, user_id=user.id
+    )
+    db.session.flush()
+
+    fel.refused_email_id = refused_email.id
+    db.session.commit()
+
+    LOG.d("Create refused email %s", refused_email)
+
+    refused_email_url = (
+        URL + f"/dashboard/refused_email?highlight_fel_id=" + str(fel.id)
+    )
+
     # inform user if this is the first bounced email
     # inform user if this is the first bounced email
     if nb_bounced == 1:
     if nb_bounced == 1:
         LOG.d(
         LOG.d(
@@ -530,7 +560,9 @@ def handle_bounce(
             alias,
             alias,
         )
         )
         send_email(
         send_email(
-            mailbox_email,
+            # TOOD: use mailbox_email instead
+            user.email,
+            # mailbox_email,
             f"Email from {forward_email.website_from} to {alias} cannot be delivered to your inbox",
             f"Email from {forward_email.website_from} to {alias} cannot be delivered to your inbox",
             render(
             render(
                 "transactional/bounced-email.txt",
                 "transactional/bounced-email.txt",
@@ -539,6 +571,7 @@ def handle_bounce(
                 website_from=forward_email.website_from,
                 website_from=forward_email.website_from,
                 website_email=forward_email.website_email,
                 website_email=forward_email.website_email,
                 disable_alias_link=disable_alias_link,
                 disable_alias_link=disable_alias_link,
+                refused_email_url=refused_email_url,
             ),
             ),
             render(
             render(
                 "transactional/bounced-email.html",
                 "transactional/bounced-email.html",
@@ -547,8 +580,10 @@ def handle_bounce(
                 website_from=forward_email.website_from,
                 website_from=forward_email.website_from,
                 website_email=forward_email.website_email,
                 website_email=forward_email.website_email,
                 disable_alias_link=disable_alias_link,
                 disable_alias_link=disable_alias_link,
+                refused_email_url=refused_email_url,
             ),
             ),
-            bounced_email=msg,
+            # cannot include bounce email as it can contain spammy text
+            # bounced_email=msg,
         )
         )
     # disable the alias the second time email is bounced
     # disable the alias the second time email is bounced
     elif nb_bounced >= 2:
     elif nb_bounced >= 2:

+ 3 - 1
templates/emails/transactional/bounced-email.html

@@ -4,7 +4,9 @@
   {{ render_text("Hi " + name) }}
   {{ render_text("Hi " + name) }}
   {{ render_text("An email sent to your alias <b>" + alias + "</b> from <b>" + website_email + "</b> was <b>refused</b> (or <em>bounced</em>) by your email provider.") }}
   {{ render_text("An email sent to your alias <b>" + alias + "</b> from <b>" + website_email + "</b> was <b>refused</b> (or <em>bounced</em>) by your email provider.") }}
 
 
-  {{ render_text("This is usually due to the email being considered as <b>spam</b> by your email provider. The email is included at the end  of this message so you can take a look at its content.") }}
+  {{ render_text('This is usually due to the email being considered as <b>spam</b> by your email provider.') }}
+
+  {{ render_button("View the refused email", refused_email_url) }}
 
 
   {{ render_text('To avoid spams forwarded by SimpleLogin server, please consider the following options:') }}
   {{ render_text('To avoid spams forwarded by SimpleLogin server, please consider the following options:') }}
 
 

+ 2 - 1
templates/emails/transactional/bounced-email.txt

@@ -3,7 +3,8 @@ Hi {{name}}
 An email sent to your alias {{alias}} from {{website_from}} was refused (or bounced) by your email provider.
 An email sent to your alias {{alias}} from {{website_from}} was refused (or bounced) by your email provider.
 
 
 This is usually due to the email being considered as spam by your email provider.
 This is usually due to the email being considered as spam by your email provider.
-The email is included at the end  of this message so you can take a look at its content.
+You can view this email here:
+{{ refused_email_url }}
 
 
 To avoid spams forwarded by SimpleLogin server, please consider the following options:
 To avoid spams forwarded by SimpleLogin server, please consider the following options: