@@ -19,6 +19,33 @@ function sanitizeSubject(value: string): string {
1919 return value . replace ( / \s + / g, " " ) . trim ( ) ;
2020}
2121
22+ function buildInternalEmailHtml ( options : {
23+ heading : string ,
24+ fields : Array < { label : string , value : string } | { label : string , href : string , linkText : string } > ,
25+ contentLabel : string ,
26+ contentBody : string ,
27+ } ) : string {
28+ const fieldRows = options . fields . map ( ( field ) => {
29+ if ( "href" in field ) {
30+ return `<p><strong>${ escapeHtml ( field . label ) } :</strong> <a href="${ escapeHtml ( field . href ) } ">${ escapeHtml ( field . linkText ) } </a></p>` ;
31+ }
32+ return `<p><strong>${ escapeHtml ( field . label ) } :</strong> ${ formatTextForHtml ( field . value ) } </p>` ;
33+ } ) . join ( "\n " ) ;
34+
35+ return `
36+ <div style="font-family: Arial, sans-serif; max-width: 720px; margin: 0 auto; padding: 24px; color: #1f2937;">
37+ <h2 style="margin: 0 0 20px;">${ escapeHtml ( options . heading ) } </h2>
38+ ${ fieldRows }
39+ <div style="margin-top: 24px;">
40+ <p style="margin-bottom: 8px;"><strong>${ escapeHtml ( options . contentLabel ) } </strong></p>
41+ <div style="padding: 16px; border: 1px solid #d1d5db; border-radius: 8px; background: #f9fafb; white-space: normal;">
42+ ${ formatTextForHtml ( options . contentBody ) }
43+ </div>
44+ </div>
45+ </div>
46+ ` ;
47+ }
48+
2249export function getInternalFeedbackRecipients ( ) : string [ ] {
2350 const rawRecipients = getEnvVariable ( "STACK_INTERNAL_FEEDBACK_RECIPIENTS" , defaultRecipient ) ;
2451 const recipients = rawRecipients . split ( "," ) . map ( ( recipient ) => recipient . trim ( ) ) ;
@@ -65,26 +92,21 @@ export async function sendSupportFeedbackEmail(options: {
6592 message : string ,
6693} ) {
6794 const displayName = options . name ?? options . user . display_name ?? "Not provided" ;
68- const htmlContent = `
69- <div style="font-family: Arial, sans-serif; max-width: 720px; margin: 0 auto; padding: 24px; color: #1f2937;">
70- <h2 style="margin: 0 0 20px;">Support feedback submission</h2>
71- <p><strong>Sender name:</strong> ${ formatTextForHtml ( displayName ) } </p>
72- <p><strong>Sender email:</strong> ${ formatTextForHtml ( options . email ) } </p>
73- <p><strong>Stack Auth user ID:</strong> ${ formatTextForHtml ( options . user . id ) } </p>
74- <p><strong>Stack Auth display name:</strong> ${ formatTextForHtml ( options . user . display_name ?? "Not provided" ) } </p>
75- <div style="margin-top: 24px;">
76- <p style="margin-bottom: 8px;"><strong>Message</strong></p>
77- <div style="padding: 16px; border: 1px solid #d1d5db; border-radius: 8px; background: #f9fafb; white-space: normal;">
78- ${ formatTextForHtml ( options . message ) }
79- </div>
80- </div>
81- </div>
82- ` ;
8395
8496 await sendInternalOperationsEmail ( {
8597 tenancy : options . tenancy ,
8698 subject : `[Support] ${ options . email } ` ,
87- htmlContent,
99+ htmlContent : buildInternalEmailHtml ( {
100+ heading : "Support feedback submission" ,
101+ fields : [
102+ { label : "Sender name" , value : displayName } ,
103+ { label : "Sender email" , value : options . email } ,
104+ { label : "Stack Auth user ID" , value : options . user . id } ,
105+ { label : "Stack Auth display name" , value : options . user . display_name ?? "Not provided" } ,
106+ ] ,
107+ contentLabel : "Message" ,
108+ contentBody : options . message ,
109+ } ) ,
88110 } ) ;
89111}
90112
@@ -96,28 +118,23 @@ export async function sendFeatureRequestNotificationEmail(options: {
96118 featureRequestId : string ,
97119} ) {
98120 const featureRequestUrl = new URL ( urlString `/p/${ options . featureRequestId } ` , "https://feedback.stack-auth.com" ) . toString ( ) ;
99- const htmlContent = `
100- <div style="font-family: Arial, sans-serif; max-width: 720px; margin: 0 auto; padding: 24px; color: #1f2937;">
101- <h2 style="margin: 0 0 20px;">New feature request submitted</h2>
102- <p><strong>Title:</strong> ${ formatTextForHtml ( options . title ) } </p>
103- <p><strong>Featurebase post ID:</strong> ${ formatTextForHtml ( options . featureRequestId ) } </p>
104- <p><strong>Featurebase URL:</strong> <a href="${ escapeHtml ( featureRequestUrl ) } ">${ escapeHtml ( featureRequestUrl ) } </a></p>
105- <p><strong>Submitted by:</strong> ${ formatTextForHtml ( options . user . display_name ?? "Not provided" ) } </p>
106- <p><strong>Submitted email:</strong> ${ formatTextForHtml ( options . user . primary_email ?? "Not provided" ) } </p>
107- <p><strong>Stack Auth user ID:</strong> ${ formatTextForHtml ( options . user . id ) } </p>
108- <div style="margin-top: 24px;">
109- <p style="margin-bottom: 8px;"><strong>Details</strong></p>
110- <div style="padding: 16px; border: 1px solid #d1d5db; border-radius: 8px; background: #f9fafb; white-space: normal;">
111- ${ formatTextForHtml ( options . content ?? "Not provided" ) }
112- </div>
113- </div>
114- </div>
115- ` ;
116121
117122 await sendInternalOperationsEmail ( {
118123 tenancy : options . tenancy ,
119124 subject : `[Feature Request] ${ options . title } ` ,
120- htmlContent,
125+ htmlContent : buildInternalEmailHtml ( {
126+ heading : "New feature request submitted" ,
127+ fields : [
128+ { label : "Title" , value : options . title } ,
129+ { label : "Featurebase post ID" , value : options . featureRequestId } ,
130+ { label : "Featurebase URL" , href : featureRequestUrl , linkText : featureRequestUrl } ,
131+ { label : "Submitted by" , value : options . user . display_name ?? "Not provided" } ,
132+ { label : "Submitted email" , value : options . user . primary_email ?? "Not provided" } ,
133+ { label : "Stack Auth user ID" , value : options . user . id } ,
134+ ] ,
135+ contentLabel : "Details" ,
136+ contentBody : options . content ?? "Not provided" ,
137+ } ) ,
121138 } ) ;
122139}
123140
0 commit comments