use VERP for transactional email: remove SENDER, SENDER_DIR
This commit is contained in:
parent
3e1ef3358b
commit
4cd49b66c2
|
@ -60,6 +60,17 @@ BOUNCE_PREFIX = os.environ.get("BOUNCE_PREFIX") or "bounce+"
|
|||
BOUNCE_SUFFIX = os.environ.get("BOUNCE_SUFFIX") or f"+@{EMAIL_DOMAIN}"
|
||||
BOUNCE_EMAIL = BOUNCE_PREFIX + "{}" + BOUNCE_SUFFIX
|
||||
|
||||
# VERP for transactional email: mail_from set to BOUNCE_PREFIX + email_log.id + BOUNCE_SUFFIX
|
||||
TRANSACTIONAL_BOUNCE_PREFIX = (
|
||||
os.environ.get("TRANSACTIONAL_BOUNCE_PREFIX") or "transactional+"
|
||||
)
|
||||
TRANSACTIONAL_BOUNCE_SUFFIX = (
|
||||
os.environ.get("TRANSACTIONAL_BOUNCE_SUFFIX") or f"+@{EMAIL_DOMAIN}"
|
||||
)
|
||||
TRANSACTIONAL_BOUNCE_EMAIL = (
|
||||
TRANSACTIONAL_BOUNCE_PREFIX + "{}" + TRANSACTIONAL_BOUNCE_SUFFIX
|
||||
)
|
||||
|
||||
try:
|
||||
MAX_NB_EMAIL_FREE_PLAN = int(os.environ["MAX_NB_EMAIL_FREE_PLAN"])
|
||||
except Exception:
|
||||
|
@ -69,12 +80,6 @@ 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
|
||||
|
|
|
@ -120,7 +120,13 @@ def directory():
|
|||
|
||||
if Directory.get_by(name=new_dir_name):
|
||||
flash(f"{new_dir_name} already added", "warning")
|
||||
elif new_dir_name in ("reply", "ra", "bounces", "bounce"):
|
||||
elif new_dir_name in (
|
||||
"reply",
|
||||
"ra",
|
||||
"bounces",
|
||||
"bounce",
|
||||
"transactional",
|
||||
):
|
||||
flash(
|
||||
"this directory name is reserved, please choose another name",
|
||||
"warning",
|
||||
|
|
|
@ -32,11 +32,11 @@ from app.config import (
|
|||
DISPOSABLE_EMAIL_DOMAINS,
|
||||
MAX_ALERT_24H,
|
||||
POSTFIX_PORT,
|
||||
SENDER,
|
||||
URL,
|
||||
LANDING_PAGE_URL,
|
||||
EMAIL_DOMAIN,
|
||||
ALERT_DIRECTORY_DISABLED_ALIAS_CREATION,
|
||||
TRANSACTIONAL_BOUNCE_EMAIL,
|
||||
)
|
||||
from app.dns_utils import get_mx_domains
|
||||
from app.extensions import db
|
||||
|
@ -50,6 +50,7 @@ from app.models import (
|
|||
Contact,
|
||||
Alias,
|
||||
EmailLog,
|
||||
TransactionalEmail,
|
||||
)
|
||||
from app.utils import (
|
||||
random_string,
|
||||
|
@ -230,6 +231,7 @@ def send_email(
|
|||
unsubscribe_link=None,
|
||||
unsubscribe_via_email=False,
|
||||
):
|
||||
to_email = sanitize_email(to_email)
|
||||
if NOT_SEND_EMAIL:
|
||||
LOG.d(
|
||||
"send email with subject '%s' to '%s', plaintext: %s",
|
||||
|
@ -277,10 +279,13 @@ def send_email(
|
|||
add_dkim_signature(msg, email_domain)
|
||||
|
||||
msg_raw = to_bytes(msg)
|
||||
if SENDER:
|
||||
smtp.sendmail(SENDER, to_email, msg_raw)
|
||||
else:
|
||||
smtp.sendmail(SUPPORT_EMAIL, to_email, msg_raw)
|
||||
|
||||
transaction = TransactionalEmail.get_by(email=to_email)
|
||||
if not transaction:
|
||||
transaction = TransactionalEmail.create(email=to_email, commit=True)
|
||||
|
||||
# use a different envelope sender for each transactional email (aka VERP)
|
||||
smtp.sendmail(TRANSACTIONAL_BOUNCE_EMAIL.format(transaction.id), to_email, msg_raw)
|
||||
|
||||
|
||||
def send_email_with_rate_control(
|
||||
|
|
|
@ -2114,8 +2114,7 @@ class Metric(db.Model, ModelMixin):
|
|||
|
||||
|
||||
class Bounce(db.Model, ModelMixin):
|
||||
"""Record all bounces. Deleted after 7 days
|
||||
"""
|
||||
"""Record all bounces. Deleted after 7 days"""
|
||||
|
||||
email = db.Column(db.String(256), nullable=False, index=True)
|
||||
|
||||
|
|
|
@ -68,8 +68,6 @@ from app.config import (
|
|||
ALERT_SPAM_EMAIL,
|
||||
ALERT_SPF,
|
||||
POSTFIX_PORT,
|
||||
SENDER,
|
||||
SENDER_DIR,
|
||||
SPAMASSASSIN_HOST,
|
||||
MAX_SPAM_SCORE,
|
||||
MAX_REPLY_PHASE_SPAM_SCORE,
|
||||
|
@ -81,6 +79,8 @@ from app.config import (
|
|||
BOUNCE_EMAIL,
|
||||
BOUNCE_PREFIX,
|
||||
BOUNCE_SUFFIX,
|
||||
TRANSACTIONAL_BOUNCE_PREFIX,
|
||||
TRANSACTIONAL_BOUNCE_SUFFIX,
|
||||
)
|
||||
from app.email_utils import (
|
||||
send_email,
|
||||
|
@ -1502,26 +1502,21 @@ def handle_unsubscribe_user(user_id: int, mail_from: str) -> str:
|
|||
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)
|
||||
def handle_transactional_bounce(envelope: Envelope, rcpt_to):
|
||||
LOG.d("handle transactional bounce sent to %s", rcpt_to)
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(envelope.original_content)
|
||||
# parse the TransactionalEmail
|
||||
transactional_id = parse_id_from_bounce(rcpt_to)
|
||||
transactional = TransactionalEmail.get(transactional_id)
|
||||
|
||||
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
|
||||
if transactional:
|
||||
LOG.info("Create bounce for %s", transactional.email)
|
||||
Bounce.create(email=transactional.email, commit=True)
|
||||
else:
|
||||
LOG.exception(
|
||||
"Cannot find transactional email for %s %s", transactional_id, rcpt_to
|
||||
)
|
||||
|
||||
return "250 email to sender accepted"
|
||||
|
||||
|
||||
def handle(envelope: Envelope) -> str:
|
||||
"""Return SMTP status"""
|
||||
|
@ -1538,9 +1533,14 @@ def handle(envelope: Envelope) -> str:
|
|||
return handle_unsubscribe(envelope)
|
||||
|
||||
# emails sent to sender. Probably bounce emails
|
||||
if SENDER and rcpt_tos == [SENDER]:
|
||||
if (
|
||||
len(rcpt_tos) == 1
|
||||
and rcpt_tos[0].startswith(TRANSACTIONAL_BOUNCE_PREFIX)
|
||||
and rcpt_tos[0].endswith(TRANSACTIONAL_BOUNCE_SUFFIX)
|
||||
):
|
||||
LOG.d("Handle email sent to sender from %s", mail_from)
|
||||
return handle_sender_email(envelope)
|
||||
handle_transactional_bounce(envelope, rcpt_tos[0])
|
||||
return "250 bounce handled"
|
||||
|
||||
if (
|
||||
len(rcpt_tos) == 1
|
||||
|
|
|
@ -36,8 +36,6 @@ PREMIUM_ALIAS_DOMAINS=["premium.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
|
||||
|
||||
# To use VERP
|
||||
# prefix must end with + and suffix must start with +
|
||||
|
@ -45,9 +43,6 @@ SENDER=sender@sl.local
|
|||
# BOUNCE_SUFFIX = "+@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 New Issue