Merge pull request #226 from simple-login/sender-report
Handle transactional bounce emails
This commit is contained in:
commit
2034225a37
|
@ -62,6 +62,12 @@ except Exception:
|
|||
# maximum number of directory a premium user can create
|
||||
MAX_NB_DIRECTORY = 50
|
||||
|
||||
# transactional email sender
|
||||
SENDER = os.environ.get("SENDER")
|
||||
|
||||
# the directory to store bounce emails
|
||||
SENDER_DIR = os.environ.get("SENDER_DIR")
|
||||
|
||||
ENFORCE_SPF = "ENFORCE_SPF" in os.environ
|
||||
|
||||
# allow to override postfix server locally
|
||||
|
|
|
@ -27,6 +27,7 @@ from app.config import (
|
|||
DISPOSABLE_EMAIL_DOMAINS,
|
||||
MAX_ALERT_24H,
|
||||
POSTFIX_PORT,
|
||||
SENDER,
|
||||
)
|
||||
from app.dns_utils import get_mx_domains
|
||||
from app.extensions import db
|
||||
|
@ -180,9 +181,7 @@ def send_cannot_create_domain_alias(user, alias, domain):
|
|||
)
|
||||
|
||||
|
||||
def send_email(
|
||||
to_email, subject, plaintext, html=None, bounced_email: Optional[Message] = None
|
||||
):
|
||||
def send_email(to_email, subject, plaintext, html=None):
|
||||
if NOT_SEND_EMAIL:
|
||||
LOG.d(
|
||||
"send email with subject %s to %s, plaintext: %s",
|
||||
|
@ -200,26 +199,12 @@ def send_email(
|
|||
else:
|
||||
smtp = SMTP(POSTFIX_SERVER, POSTFIX_PORT or 25)
|
||||
|
||||
if bounced_email:
|
||||
msg = MIMEMultipart("mixed")
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg.attach(MIMEText(plaintext, "text"))
|
||||
|
||||
# add email main body
|
||||
body = MIMEMultipart("alternative")
|
||||
body.attach(MIMEText(plaintext, "text"))
|
||||
if html:
|
||||
body.attach(MIMEText(html, "html"))
|
||||
|
||||
msg.attach(body)
|
||||
|
||||
# add attachment
|
||||
rfcmessage = MIMEBase("message", "rfc822")
|
||||
rfcmessage.attach(bounced_email)
|
||||
msg.attach(rfcmessage)
|
||||
else:
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg.attach(MIMEText(plaintext, "text"))
|
||||
if html:
|
||||
msg.attach(MIMEText(html, "html"))
|
||||
if not html:
|
||||
html = plaintext.replace("\n", "<br>")
|
||||
msg.attach(MIMEText(html, "html"))
|
||||
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = f"{SUPPORT_NAME} <{SUPPORT_EMAIL}>"
|
||||
|
@ -236,7 +221,10 @@ def send_email(
|
|||
add_dkim_signature(msg, email_domain)
|
||||
|
||||
msg_raw = msg.as_bytes()
|
||||
smtp.sendmail(SUPPORT_EMAIL, to_email, msg_raw)
|
||||
if SENDER:
|
||||
smtp.sendmail(SENDER, to_email, msg_raw)
|
||||
else:
|
||||
smtp.sendmail(SUPPORT_EMAIL, to_email, msg_raw)
|
||||
|
||||
|
||||
def send_email_with_rate_control(
|
||||
|
@ -246,7 +234,6 @@ def send_email_with_rate_control(
|
|||
subject,
|
||||
plaintext,
|
||||
html=None,
|
||||
bounced_email: Optional[Message] = None,
|
||||
max_alert_24h=MAX_ALERT_24H,
|
||||
) -> bool:
|
||||
"""Same as send_email with rate control over alert_type.
|
||||
|
@ -273,7 +260,7 @@ def send_email_with_rate_control(
|
|||
|
||||
SentAlert.create(user_id=user.id, alert_type=alert_type, to_email=to_email)
|
||||
db.session.commit()
|
||||
send_email(to_email, subject, plaintext, html, bounced_email)
|
||||
send_email(to_email, subject, plaintext, html)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ It should contain the following info:
|
|||
|
||||
"""
|
||||
import email
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
from email import encoders
|
||||
|
@ -63,6 +64,8 @@ from app.config import (
|
|||
ALERT_SPAM_EMAIL,
|
||||
ALERT_SPF,
|
||||
POSTFIX_PORT,
|
||||
SENDER,
|
||||
SENDER_DIR,
|
||||
)
|
||||
from app.email_utils import (
|
||||
send_email,
|
||||
|
@ -836,7 +839,6 @@ def handle_bounce(contact: Contact, alias: Alias, msg: Message, user: User):
|
|||
send_email_with_rate_control(
|
||||
user,
|
||||
ALERT_BOUNCE_EMAIL,
|
||||
# use user mail here as only user is authenticated to see the refused email
|
||||
user.email,
|
||||
f"Email from {contact.website_email} to {address} cannot be delivered to your inbox",
|
||||
render(
|
||||
|
@ -857,8 +859,6 @@ def handle_bounce(contact: Contact, alias: Alias, msg: Message, user: User):
|
|||
refused_email_url=refused_email_url,
|
||||
mailbox_email=mailbox.email,
|
||||
),
|
||||
# cannot include bounce email as it can contain spammy text
|
||||
# bounced_email=msg,
|
||||
)
|
||||
# disable the alias the second time email is bounced
|
||||
elif nb_bounced >= 2:
|
||||
|
@ -876,7 +876,6 @@ def handle_bounce(contact: Contact, alias: Alias, msg: Message, user: User):
|
|||
send_email_with_rate_control(
|
||||
user,
|
||||
ALERT_BOUNCE_EMAIL,
|
||||
# use user mail here as only user is authenticated to see the refused email
|
||||
user.email,
|
||||
f"Alias {address} has been disabled due to second undelivered email from {contact.website_email}",
|
||||
render(
|
||||
|
@ -895,8 +894,6 @@ def handle_bounce(contact: Contact, alias: Alias, msg: Message, user: User):
|
|||
refused_email_url=refused_email_url,
|
||||
mailbox_email=mailbox.email,
|
||||
),
|
||||
# cannot include bounce email as it can contain spammy text
|
||||
# bounced_email=msg,
|
||||
)
|
||||
|
||||
|
||||
|
@ -1025,6 +1022,27 @@ def handle_unsubscribe(envelope: Envelope):
|
|||
return "250 Unsubscribe request accepted"
|
||||
|
||||
|
||||
def handle_sender_email(envelope: Envelope):
|
||||
filename = (
|
||||
arrow.now().format("YYYY-MM-DD_HH-mm-ss") + "_" + random_string(10) + ".eml"
|
||||
)
|
||||
filepath = os.path.join(SENDER_DIR, filename)
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(envelope.original_content)
|
||||
|
||||
LOG.d("Write email to sender at %s", filepath)
|
||||
|
||||
msg = email.message_from_bytes(envelope.original_content)
|
||||
orig = get_orig_message_from_bounce(msg)
|
||||
if orig:
|
||||
LOG.warning(
|
||||
"Original message %s -> %s saved at %s", orig["From"], orig["To"], filepath
|
||||
)
|
||||
|
||||
return "250 email to sender accepted"
|
||||
|
||||
|
||||
def handle(envelope: Envelope, smtp: SMTP) -> str:
|
||||
"""Return SMTP status"""
|
||||
# unsubscribe request
|
||||
|
@ -1032,6 +1050,11 @@ def handle(envelope: Envelope, smtp: SMTP) -> str:
|
|||
LOG.d("Handle unsubscribe request from %s", envelope.mail_from)
|
||||
return handle_unsubscribe(envelope)
|
||||
|
||||
# emails sent to sender. Probably bounce emails
|
||||
if SENDER and envelope.rcpt_tos == [SENDER]:
|
||||
LOG.d("Handle email sent to sender from %s", envelope.mail_from)
|
||||
return handle_sender_email(envelope)
|
||||
|
||||
# Whether it's necessary to apply greylisting
|
||||
if greylisting_needed(envelope.mail_from, envelope.rcpt_tos):
|
||||
LOG.warning(
|
||||
|
|
|
@ -33,6 +33,11 @@ ALIAS_DOMAINS=["domain1.com", "domain2.com"]
|
|||
# transactional email is sent from this email address
|
||||
SUPPORT_EMAIL=support@sl.local
|
||||
SUPPORT_NAME=Son from SimpleLogin
|
||||
# in case sender is different than SUPPORT_EMAIL
|
||||
SENDER=sender@sl.local
|
||||
|
||||
# all emails sent to sender are stored in this folder
|
||||
SENDER_DIR=/tmp
|
||||
|
||||
# to receive general stats.
|
||||
# ADMIN_EMAIL=admin@sl.local
|
||||
|
|
Loading…
Reference in a new issue