handle_bounce_reply_phase

This commit is contained in:
Son NK 2020-11-04 12:32:15 +01:00
parent f4f2db0f04
commit 6c626520d3
4 changed files with 140 additions and 15 deletions

View File

@ -292,6 +292,8 @@ ALERT_REVERSE_ALIAS_UNKNOWN_MAILBOX = "reverse_alias_unknown_mailbox"
# When a forwarding email is bounced
ALERT_BOUNCE_EMAIL = "bounce"
ALERT_BOUNCE_EMAIL_REPLY_PHASE = "bounce-when-reply"
# When a forwarding email is detected as spam
ALERT_SPAM_EMAIL = "spam"

View File

@ -77,6 +77,7 @@ from app.config import (
ALERT_SEND_EMAIL_CYCLE,
ALERT_MAILBOX_IS_ALIAS,
PGP_SENDER_PRIVATE_KEY,
ALERT_BOUNCE_EMAIL_REPLY_PHASE,
)
from app.email_utils import (
send_email,
@ -528,10 +529,10 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str
LOG.d("alias %s cannot be created on-the-fly, return 550", address)
return [(False, "550 SL E3 Email not exist")]
if alias.user.disabled:
LOG.warning(
"User %s disabled, disable forwarding emails for %s", alias.user, alias
)
user = alias.user
if user.disabled:
LOG.warning("User %s disabled, disable forwarding emails for %s", user, alias)
return [(False, "550 SL E20 Account disabled")]
mail_from = envelope.mail_from
@ -539,7 +540,7 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str
# email send from a mailbox to alias
if mb.email == mail_from:
LOG.warning("cycle email sent from %s to %s", mb, alias)
handle_email_sent_to_ourself(alias, mb, msg, alias.user)
handle_email_sent_to_ourself(alias, mb, msg, user)
return [(True, "250 Message accepted for delivery")]
# bounce email initiated by Postfix
@ -547,14 +548,12 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str
# in this case Postfix will send a bounce report to original sender, which is the alias
if mail_from == "<>":
LOG.exception("Bounce email sent to %s", alias)
# save the data for debugging
file_path = f"/tmp/{random_string(10)}.eml"
with open(file_path, "wb") as f:
f.write(msg.as_bytes())
return [(False, "421 SL Retry later")]
handle_bounce_reply_phase(alias, msg, user)
return [(False, "550 SL E24 Email cannot be sent to contact")]
contact = get_or_create_contact(msg["From"], envelope.mail_from, alias)
email_log = EmailLog.create(contact_id=contact.id, user_id=contact.user_id)
db.session.commit()
@ -566,8 +565,6 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str
# do not return 5** to allow user to receive emails later when alias is enabled
return [(True, "250 Message accepted for delivery")]
user = alias.user
ret = []
mailboxes = alias.mailboxes
@ -939,6 +936,9 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
# return 421 so the client can retry later
return False, "421 SL E13 Retry later"
# save the email_log to DB
db.session.commit()
# make the email comes from alias
from_header = alias.email
# add alias name from alias
@ -962,11 +962,12 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
_MESSAGE_ID,
make_msgid(str(email_log.id), get_email_domain_part(alias.email)),
)
add_or_replace_header(msg, _EMAIL_LOG_ID_HEADER, str(email_log.id))
date_header = formatdate()
msg["Date"] = date_header
add_or_replace_header(msg, _DIRECTION, "Reply")
msg[_DIRECTION] = "Reply"
msg[_MAILBOX_ID_HEADER] = str(mailbox.id)
msg[_EMAIL_LOG_ID_HEADER] = str(email_log.id)
LOG.d(
"send email from %s to %s, mail_options:%s,rcpt_options:%s",
@ -1012,7 +1013,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
)
# return 250 even if error as user is already informed of the incident and can retry sending the email
db.session.commit()
return True, "250 Message accepted for delivery"
@ -1344,6 +1345,80 @@ def handle_bounce(contact: Contact, alias: Alias, msg: Message, user: User):
)
def handle_bounce_reply_phase(alias: Alias, msg: Message, user: User):
"""
Handle bounce that is sent to alias
Happens when an email cannot be sent from an alias to a contact
"""
# Store the bounced email
# generate a name for the email
random_name = str(uuid.uuid4())
full_report_path = f"refused-emails/full-{random_name}.eml"
s3.upload_email_from_bytesio(full_report_path, BytesIO(msg.as_bytes()), random_name)
orig_msg = get_orig_message_from_bounce(msg)
file_path = f"refused-emails/{random_name}.eml"
s3.upload_email_from_bytesio(file_path, BytesIO(orig_msg.as_bytes()), random_name)
email_log_id = int(orig_msg[_EMAIL_LOG_ID_HEADER])
email_log = EmailLog.get(email_log_id)
contact = email_log.contact
try:
mailbox_id = int(orig_msg[_MAILBOX_ID_HEADER])
except TypeError:
LOG.warning(
"cannot parse mailbox from original message header %s",
orig_msg[_MAILBOX_ID_HEADER],
)
# fall back to the default mailbox
mailbox = alias.mailbox
else:
mailbox = Mailbox.get(mailbox_id)
email_log.bounced_mailbox_id = mailbox.id
refused_email = RefusedEmail.create(
path=file_path, full_report_path=full_report_path, user_id=user.id
)
db.session.flush()
LOG.d("Create refused email %s", refused_email)
email_log.bounced = True
email_log.refused_email_id = refused_email.id
db.session.commit()
refused_email_url = (
URL + f"/dashboard/refused_email?highlight_id=" + str(email_log.id)
)
LOG.d(
"Inform user %s about bounced email sent by %s to %s",
user,
alias,
contact,
)
send_email_with_rate_control(
user,
ALERT_BOUNCE_EMAIL_REPLY_PHASE,
mailbox.email,
f"Email cannot be sent to { contact.email } from your alias { alias.email }",
render(
"transactional/bounce-email-reply-phase.txt",
alias=alias,
contact=contact,
refused_email_url=refused_email_url,
),
render(
"transactional/bounce-email-reply-phase.html",
alias=alias,
contact=contact,
refused_email_url=refused_email_url,
),
)
def handle_spam(
contact: Contact,
alias: Alias,

View File

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
Email cannot be sent to {{ contact.email }} from your alias {{ alias.email }}
</h1>
{% endcall %}
{% call text() %}
This might mean {{ contact.email }}
<ul>
<li>is not a valid email address, or</li>
<li>doesn't exist, or</li>
<li>its mail server refuses your email</li>
</ul>
{% endcall %}
{{ render_button("View the original email", refused_email_url) }}
{% call text() %}
This email is automatically deleted in 7 days.
{% endcall %}
{% call text() %}
Best, <br/>
SimpleLogin Team.
{% endcall %}
{% endblock %}

View File

@ -0,0 +1,16 @@
Email cannot be sent to {{ contact.email }} from your alias {{ alias.email }}
This might mean {{ contact.email }}
- is not a valid address, or
- doesn't exist, or
- its mail server refuses your email.
You can view the email at {{refused_email_url}}.
This email is automatically deleted in 7 days.
Best,
SimpleLogin Team.