handle_bounce_reply_phase
This commit is contained in:
parent
f4f2db0f04
commit
6c626520d3
|
@ -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"
|
||||
|
||||
|
|
105
email_handler.py
105
email_handler.py
|
@ -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,
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue