From 1e7eeb8daa32e5a252729f607340fa4fccc6de2b Mon Sep 17 00:00:00 2001 From: chihlasm Date: Wed, 18 Feb 2026 17:41:30 -0500 Subject: [PATCH] feat: add feedback notification and confirmation emails to EmailService Co-Authored-By: Claude Opus 4.6 --- backend/app/core/email.py | 189 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/backend/app/core/email.py b/backend/app/core/email.py index 4ad3d92d..a5dc036a 100644 --- a/backend/app/core/email.py +++ b/backend/app/core/email.py @@ -163,6 +163,92 @@ class EmailService: logger.exception("Failed to send account invite email to %s", to_email) return False + @staticmethod + async def send_feedback_email( + to_email: str, + reply_to_email: str, + feedback_type: str, + message: str, + user_email: str, + account_name: str | None = None, + account_code: str | None = None, + ) -> bool: + if not settings.email_enabled: + logger.warning("Email not sent — RESEND_API_KEY not configured") + return False + + try: + import resend + from datetime import datetime, timezone + + resend.api_key = settings.RESEND_API_KEY + + date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d") + code_suffix = f" — {account_code}" if account_code else "" + subject = f"[ResolutionFlow Feedback] {feedback_type} — {date_str}{code_suffix}" + + html = _render_feedback_html( + feedback_type=feedback_type, + message=message, + user_email=user_email, + account_name=account_name, + account_code=account_code, + ) + + resend.Emails.send( + { + "from": settings.FROM_EMAIL, + "to": [to_email], + "reply_to": reply_to_email, + "subject": subject, + "html": html, + } + ) + logger.info("Feedback email sent from %s (type: %s)", user_email, feedback_type) + return True + + except Exception: + logger.exception("Failed to send feedback email from %s", user_email) + return False + + @staticmethod + async def send_feedback_confirmation_email( + to_email: str, + feedback_type: str, + message_preview: str, + ) -> bool: + """Send a thank-you confirmation to the feedback submitter. Fire-and-forget.""" + if not settings.email_enabled: + logger.warning("Confirmation email not sent — RESEND_API_KEY not configured") + return False + + try: + import resend + + resend.api_key = settings.RESEND_API_KEY + + subject = "Thanks for your feedback — ResolutionFlow" + + html = _render_feedback_confirmation_html( + feedback_type=feedback_type, + message_preview=message_preview, + ) + + resend.Emails.send( + { + "from": settings.FROM_EMAIL, + "to": [to_email], + "subject": subject, + "html": html, + } + ) + logger.info("Feedback confirmation email sent to %s", to_email) + return True + + except Exception: + logger.exception("Failed to send feedback confirmation to %s", to_email) + return False + def _render_invite_html( code: str, @@ -334,3 +420,106 @@ def _render_password_reset_html(reset_url: str) -> str: """ + + +def _render_feedback_html( + feedback_type: str, + message: str, + user_email: str, + account_name: str | None, + account_code: str | None, +) -> str: + from datetime import datetime, timezone + import html + + date_str = datetime.now(timezone.utc).strftime("%B %d, %Y") + safe_message = html.escape(message).replace("\n", "
") + + account_line = "" + if account_name and account_code: + account_line = f""" + +

+ Account: {html.escape(account_name)} ({html.escape(account_code)}) +

+ """ + + return f""" + + + + +
+ + + + + {account_line} + + + +
+

ResolutionFlow Feedback

+
+

+ Type: {html.escape(feedback_type)} +

+
+

+ From: {html.escape(user_email)} +

+
+

+ Date: {date_str} +

+
+
+

{safe_message}

+
+
+

+ Reply directly to this email to respond to the user. +

+
+
+""" + + +def _render_feedback_confirmation_html( + feedback_type: str, + message_preview: str, +) -> str: + import html + + safe_preview = html.escape(message_preview) + + return f""" + + + + +
+ + + + + +
+

ResolutionFlow

+

Thanks for your feedback!

+
+

+ We've received your {html.escape(feedback_type)} and our team will review it shortly. +

+
+
+

Your feedback

+

"{safe_preview}"

+
+
+

+ If we need more details, we'll reach out to you directly. +

+
+
+"""