diff --git a/backend/app/core/email.py b/backend/app/core/email.py index 908715eb..5a5defae 100644 --- a/backend/app/core/email.py +++ b/backend/app/core/email.py @@ -283,6 +283,144 @@ class EmailService: return False + @staticmethod + async def send_survey_notification_email( + to_email: str, + respondent_name: str | None, + responses: dict, + ) -> bool: + """Send survey response notification. Fire-and-forget.""" + if not settings.email_enabled: + logger.warning("Email not sent — RESEND_API_KEY not configured") + return False + + try: + import resend + import html as html_mod + from datetime import datetime, timezone + + resend.api_key = settings.RESEND_API_KEY + + name_display = html_mod.escape(respondent_name) if respondent_name else "Anonymous" + date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") + subject = f"[FlowPilot Survey] New Response from {name_display} — {date_str}" + + rows_html = "" + for q_id, answer in responses.items(): + safe_id = html_mod.escape(str(q_id)) + if isinstance(answer, list): + answer_display = "
".join(f"• {html_mod.escape(str(item))}" for item in answer) + else: + answer_display = html_mod.escape(str(answer)) + rows_html += f""" + + {safe_id} + {answer_display} + """ + + email_html = f""" + + + + +
+ + + + +
+

FlowPilot Survey Response

+

From: {name_display} • {date_str}

+
+ + {rows_html} +
+
+

This response has been saved to the survey_responses table.

+
+
+""" + + resend.Emails.send({ + "from": settings.FROM_EMAIL, + "to": [to_email], + "subject": subject, + "html": email_html, + }) + logger.info("Survey notification sent for respondent: %s", name_display) + return True + + except Exception: + logger.exception("Failed to send survey notification email") + return False + + @staticmethod + async def send_survey_invite_email( + to_email: str, + recipient_name: str, + survey_url: str, + ) -> bool: + """Send survey invite email.""" + if not settings.email_enabled: + logger.warning("Email not sent — RESEND_API_KEY not configured") + return False + + try: + import resend + import html as html_mod + + resend.api_key = settings.RESEND_API_KEY + + safe_name = html_mod.escape(recipient_name) + subject = "FlowPilot Survey — Your expertise matters" + + email_html = f""" + + + + +
+ + + + + +
+

ResolutionFlow

+

FlowPilot Research

+
+

+ Hi {safe_name}, +

+

+ We're building an AI assistant for MSP engineers and would love your input. This 5-minute survey will help shape how FlowPilot thinks about troubleshooting. +

+
+ + Take the Survey + +
+

+ Your responses are confidential. Takes about 5 minutes. +

+
+
+""" + + resend.Emails.send({ + "from": settings.FROM_EMAIL, + "to": [to_email], + "subject": subject, + "html": email_html, + }) + logger.info("Survey invite email sent to %s", to_email) + return True + + except Exception: + logger.exception("Failed to send survey invite email to %s", to_email) + return False + + def _render_invite_html( code: str, plan_label: str,