diff --git a/app/config.py b/app/config.py
index bb5bef5d..a92fb17c 100644
--- a/app/config.py
+++ b/app/config.py
@@ -90,6 +90,9 @@ else:
# disable the alias suffix, i.e. the ".random_word" part
DISABLE_ALIAS_SUFFIX = "DISABLE_ALIAS_SUFFIX" in os.environ
+# the email address that receives all unsubscription request
+UNSUBSCRIBER = os.environ.get("UNSUBSCRIBER")
+
DKIM_PRIVATE_KEY_PATH = get_abs_path(os.environ["DKIM_PRIVATE_KEY_PATH"])
DKIM_PUBLIC_KEY_PATH = get_abs_path(os.environ["DKIM_PUBLIC_KEY_PATH"])
DKIM_SELECTOR = b"dkim"
diff --git a/email_handler.py b/email_handler.py
index aa0b3dea..0e4b11a3 100644
--- a/email_handler.py
+++ b/email_handler.py
@@ -52,6 +52,7 @@ from app.config import (
URL,
ALIAS_DOMAINS,
POSTFIX_SUBMISSION_TLS,
+ UNSUBSCRIBER,
)
from app.email_utils import (
send_email,
@@ -459,11 +460,15 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
add_or_replace_header(msg, "To", to_header.strip())
# add List-Unsubscribe header
- unsubscribe_link = f"{URL}/dashboard/unsubscribe/{alias.id}"
- add_or_replace_header(msg, "List-Unsubscribe", f"<{unsubscribe_link}>")
- add_or_replace_header(
- msg, "List-Unsubscribe-Post", "List-Unsubscribe=One-Click"
- )
+ if UNSUBSCRIBER:
+ unsubscribe_link = f"mailto:{UNSUBSCRIBER}?subject={alias.id}="
+ add_or_replace_header(msg, "List-Unsubscribe", f"<{unsubscribe_link}>")
+ else:
+ unsubscribe_link = f"{URL}/dashboard/unsubscribe/{alias.id}"
+ add_or_replace_header(msg, "List-Unsubscribe", f"<{unsubscribe_link}>")
+ add_or_replace_header(
+ msg, "List-Unsubscribe-Post", "List-Unsubscribe=One-Click"
+ )
add_dkim_signature(msg, EMAIL_DOMAIN)
@@ -743,6 +748,54 @@ def handle_bounce(
)
+def handle_unsubscribe(envelope):
+ message_data = envelope.content.decode("utf8", errors="replace")
+ msg = Parser(policy=SMTPUTF8).parsestr(message_data)
+
+ # format: alias_id:
+ subject = msg["Subject"]
+ try:
+ alias_id = int(subject[:-1])
+ alias = Alias.get(alias_id)
+ except Exception:
+ LOG.warning("Cannot parse alias from subject %s", msg["Subject"])
+ return "550 SL ignored"
+
+ if not alias:
+ LOG.warning("No such alias %s", alias_id)
+ return "550 SL ignored"
+
+ # This sender cannot unsubscribe
+ if alias.mailbox_email() != envelope.mail_from:
+ LOG.d("%s cannot disable alias %s", envelope.mail_from, alias)
+ return "550 SL ignored"
+
+ # Sender is owner of this alias
+ alias.enabled = False
+ db.session.commit()
+ user = alias.user
+
+ enable_alias_url = URL + f"/dashboard/?highlight_alias_id={alias.id}"
+ send_email(
+ envelope.mail_from,
+ f"Alias {alias.email} has been disabled successfully",
+ render(
+ "transactional/unsubscribe-disable-alias.txt",
+ user=user,
+ alias=alias.email,
+ enable_alias_url=enable_alias_url,
+ ),
+ render(
+ "transactional/unsubscribe-disable-alias.html",
+ user=user,
+ alias=alias.email,
+ enable_alias_url=enable_alias_url,
+ ),
+ )
+
+ return "250 Unsubscribe request accepted"
+
+
class MailHandler:
async def handle_DATA(self, server, session, envelope):
LOG.debug(
@@ -757,6 +810,14 @@ class MailHandler:
else:
smtp = SMTP(POSTFIX_SERVER, 25)
+ # unsubscribe request
+ if UNSUBSCRIBER and envelope.rcpt_tos == [UNSUBSCRIBER]:
+ LOG.d("Handle unsubscribe request from %s", envelope.mail_from)
+ app = new_app()
+
+ with app.app_context():
+ return handle_unsubscribe(envelope)
+
# result of all deliveries
# each element is a couple of whether the delivery is successful and the smtp status
res: [(bool, str)] = []
diff --git a/templates/emails/transactional/unsubscribe-disable-alias.html b/templates/emails/transactional/unsubscribe-disable-alias.html
new file mode 100644
index 00000000..dc9f6f8a
--- /dev/null
+++ b/templates/emails/transactional/unsubscribe-disable-alias.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+
+{% block content %}
+ {{ render_text("Hi " + user.name) }}
+ {{ render_text("Your alias "+ alias +" has been disabled successfully.") }}
+ {{ render_text("If this is a mistake, you can re-enable the alias on the dashboard.") }}
+ {{ render_button("Enable Alias", enable_alias_url) }}
+ {{ render_text('Thanks,
SimpleLogin Team.') }}
+ {{ raw_url(enable_alias_url) }}
+{% endblock %}
+
diff --git a/templates/emails/transactional/unsubscribe-disable-alias.txt b/templates/emails/transactional/unsubscribe-disable-alias.txt
new file mode 100644
index 00000000..b2a0841e
--- /dev/null
+++ b/templates/emails/transactional/unsubscribe-disable-alias.txt
@@ -0,0 +1,12 @@
+Hi {{user.name}}
+
+Your alias {{alias}} has been disabled successfully.
+
+If this is a mistake, you can re-enable the alias on the dashboard via
+
+{{ enable_alias_url }}
+
+Please let us know if you have any question.
+
+Best,
+SimpleLogin team.