Store the bounced email in email handling.

This commit is contained in:
Son NK 2020-03-14 16:34:23 +01:00 committed by Son NK
parent 18008460e6
commit 3aad3deb81
4 changed files with 64 additions and 11 deletions

View file

@ -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

View file

@ -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:

View file

@ -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:') }}

View file

@ -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: