aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/mail.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/mail.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/mail.py97
1 files changed, 97 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/mail.py b/packages/meshbay-hub/src/meshbay_hub/mail.py
new file mode 100644
index 0000000..ce6e264
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/mail.py
@@ -0,0 +1,97 @@
+"""
+MeshBay Hub — email sending via localhost Postfix.
+
+Postfix listens on loopback only (inet_interfaces = loopback-only), so no
+authentication is needed. See docs/MAIL-SERVER.md for the full setup.
+"""
+
+import logging
+import smtplib
+from email.message import EmailMessage
+
+log = logging.getLogger(__name__)
+
+_hub_domain: str = "meshbay.org"
+_hub_url: str = "https://meshbay.org"
+
+
+def configure(hub_id: str) -> None:
+ global _hub_domain, _hub_url
+ _hub_domain = hub_id
+ _hub_url = f"https://{hub_id}"
+
+
+def _send(msg: EmailMessage) -> bool:
+ try:
+ with smtplib.SMTP("localhost", 25, timeout=10) as s:
+ s.send_message(msg)
+ return True
+ except Exception:
+ log.exception("Failed to send email to %s", msg["To"])
+ return False
+
+
+def send_verification_code(to: str, code: str) -> None:
+ msg = EmailMessage()
+ msg["From"] = f"noreply@{_hub_domain}"
+ msg["To"] = to
+ msg["Subject"] = f"MeshBay — Your verification code: {code}"
+ msg.set_content(
+ f"Your verification code is: {code}\n"
+ "\n"
+ "Enter this code to verify your email address.\n"
+ "This code expires in 24 hours.\n"
+ "\n"
+ "If you did not create a MeshBay account, ignore this email.\n"
+ "\n"
+ f"{_hub_url}\n"
+ )
+ _send(msg)
+ log.info("Verification code sent to %s", _mask_email(to))
+
+
+def send_email_change_code(to: str, code: str) -> None:
+ msg = EmailMessage()
+ msg["From"] = f"noreply@{_hub_domain}"
+ msg["To"] = to
+ msg["Subject"] = f"MeshBay — Confirm your new email: {code}"
+ msg.set_content(
+ f"Your verification code is: {code}\n"
+ "\n"
+ "Enter this code to confirm your new email address.\n"
+ "This code expires in 24 hours.\n"
+ "\n"
+ "If you did not request this change, ignore this email.\n"
+ "\n"
+ f"{_hub_url}\n"
+ )
+ _send(msg)
+ log.info("Email change code sent to %s", _mask_email(to))
+
+
+def send_invite_notification(
+ to: str, code: str, inviter: str, group_name: str,
+) -> None:
+ msg = EmailMessage()
+ msg["From"] = f"noreply@{_hub_domain}"
+ msg["To"] = to
+ msg["Subject"] = f"MeshBay — {inviter} invited you to {group_name}"
+ msg.set_content(
+ f"{inviter} invited you to the group \"{group_name}\" on MeshBay.\n"
+ "\n"
+ f"Your one-time code is: {code}\n"
+ "\n"
+ "Open the group and enter this code when prompted.\n"
+ "The code works once and expires in 7 days.\n"
+ "\n"
+ f"{_hub_url}\n"
+ )
+ _send(msg)
+ log.info("Invite notification sent to %s", _mask_email(to))
+
+
+def _mask_email(email: str) -> str:
+ local, _, domain = email.partition("@")
+ if len(local) <= 2:
+ return f"{'*' * len(local)}@{domain}"
+ return f"{local[0]}{'*' * (len(local) - 2)}{local[-1]}@{domain}"