From eb1f2f8109cf3d92dc23b705fdc9333972cc4147 Mon Sep 17 00:00:00 2001 From: "ievgenii.v" Date: Tue, 21 Oct 2025 11:51:26 +0300 Subject: [PATCH] feature: implements sending emails to a local store for local development --- configs/development.ini | 6 ++++++ rhodecode/lib/celerylib/tasks.py | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/configs/development.ini b/configs/development.ini index 0b1619ae..a832a132 100644 --- a/configs/development.ini +++ b/configs/development.ini @@ -12,6 +12,12 @@ debug = true ; These settings will be used by the RhodeCode mailing system ; ######################################################################## +; redirect emails to a local file instead of sending them to the actual server +development_email = true + +; folder where emails will be saved locally +local_email_store = ./local_emails + ; prefix all emails subjects with given prefix, helps filtering out emails #email_prefix = [RhodeCode] diff --git a/rhodecode/lib/celerylib/tasks.py b/rhodecode/lib/celerylib/tasks.py index a7626c6f..a5f5eaef 100644 --- a/rhodecode/lib/celerylib/tasks.py +++ b/rhodecode/lib/celerylib/tasks.py @@ -28,6 +28,7 @@ from typing import Any from celery import current_app from pyramid_mailer.mailer import Mailer +from pyramid_mailer.mailer import DebugMailer from pyramid_mailer.message import Message from email.utils import formatdate @@ -77,7 +78,9 @@ def send_email(recipients, subject, body="", html_body="", email_config=None, ex email_config = email_config or rhodecode.CONFIG mail_server = email_config.get("smtp_server") or None - if mail_server is None: + dev_mod = str2bool(email_config.get("development_email")) + + if mail_server is None and not dev_mod: log.error( "SMTP server information missing. Sending email failed. " "Make sure that `smtp_server` variable is configured " @@ -104,7 +107,7 @@ def send_email(recipients, subject, body="", html_body="", email_config=None, ex recipients += admins # translate our LEGACY config into the one that pyramid_mailer supports - email_conf = dict( + transformed_email_confing = dict( host=mail_server, port=email_config.get("smtp_port", 25), username=email_config.get("smtp_username", None), @@ -135,11 +138,11 @@ def send_email(recipients, subject, body="", html_body="", email_config=None, ex extra_headers["References"] = " ".join("<{}>".format(t) for t in thread_ids) try: - mailer = Mailer(**email_conf) + mailer = _get_mailer(transformed_email_confing, email_config) message = Message( subject=subject, - sender=email_conf["default_sender"], + sender=transformed_email_confing["default_sender"], recipients=recipients, body=body, html=html_body, @@ -156,6 +159,15 @@ def send_email(recipients, subject, body="", html_body="", email_config=None, ex return True +def _get_mailer(transformed_email_conf: dict[str, Any], original_email_conf: dict[str, Any]) -> Mailer | DebugMailer: + dev_mode = str2bool(original_email_conf.get("development_email")) + + if dev_mode: + emails_path = original_email_conf.get("local_email_store") or "./local_emails" + return DebugMailer(emails_path) + return Mailer(**transformed_email_conf) + + @async_task(ignore_result=True, base=RequestContextTask) def create_repo(form_data, cur_user): from rhodecode.model.repo import RepoModel