Skip to content

Commit 4177280

Browse files
authored
Merge pull request #1 from borisolver/codex/update-email-sender-for-digital-branches
Revamp digital report alert email layout
2 parents 82e33d5 + 4e7f616 commit 4177280

File tree

2 files changed

+156
-37
lines changed

2 files changed

+156
-37
lines changed

email-service/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ type Config struct {
2424
OptOutURL string
2525
PollInterval string
2626
HTTPPort string
27+
28+
// Brand dashboard configuration
29+
BrandDashboardURL string
2730
}
2831

2932
// Load loads configuration from environment variables and flags
@@ -46,6 +49,7 @@ func Load() *Config {
4649
cfg.OptOutURL = getEnv("OPT_OUT_URL", "http://localhost:8080/opt-out")
4750
cfg.PollInterval = getEnv("POLL_INTERVAL", "10s")
4851
cfg.HTTPPort = getEnv("HTTP_PORT", "8080")
52+
cfg.BrandDashboardURL = getEnv("BRAND_DASHBOARD_URL", "https://dashboard.cleanapp.io/brand")
4953

5054
return cfg
5155
}

email-service/email/email_sender.go

Lines changed: 152 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,16 @@ func (e *EmailSender) sendOneEmailWithAnalysis(recipient string, reportImage, ma
154154

155155
// Create subject with analysis title
156156
subject := "CleanApp Report"
157+
isDigital := analysis != nil && analysis.Classification == "digital"
158+
if isDigital {
159+
subject = "CleanApp alert: major new issue reported for your brand"
160+
}
157161
if analysis.Title != "" {
158-
subject = fmt.Sprintf("CleanApp Report: %s", analysis.Title)
162+
if isDigital {
163+
subject = fmt.Sprintf("CleanApp alert: major new issue — %s", analysis.Title)
164+
} else {
165+
subject = fmt.Sprintf("CleanApp Report: %s", analysis.Title)
166+
}
159167
}
160168

161169
to := mail.NewEmail(recipient, recipient)
@@ -279,43 +287,63 @@ func (e *EmailSender) getEmailHtml(recipient string, hasReport, hasMap bool) str
279287

280288
// getEmailTextWithAnalysis returns the plain text content for emails with analysis data
281289
func (e *EmailSender) getEmailTextWithAnalysis(recipient string, analysis *models.ReportAnalysis, hasReport, hasMap bool) string {
282-
var content string
290+
if analysis.Classification == "digital" {
291+
digitalSubject := "CleanApp alert: major new issue reported for your brand"
292+
preheader := "Someone just submitted a brand-related digital report with photos."
283293

284-
attachments := ""
285-
if hasReport || hasMap {
286-
attachments = "\nThis email contains:\n"
294+
heroReport := ""
287295
if hasReport {
288-
attachments += "- The report image\n"
296+
heroReport = "\n- Hero: photo of report included."
289297
}
298+
299+
heroLocation := ""
290300
if hasMap {
291-
attachments += "- A map showing the location\n"
301+
heroLocation = "\n- Hero: photo of location included."
292302
}
293-
attachments += "- AI analysis results\n"
294-
}
295-
if analysis.Classification == "digital" {
296-
content = fmt.Sprintf(`Hello,
297303

298-
You have received a new CleanApp digital issue report with analysis.
304+
return fmt.Sprintf(`%s
305+
Preheader: %s
299306
300-
REPORT ANALYSIS:
301-
Title: %s
302-
Description: %s
303-
Type: Digital Issue
307+
Someone just submitted a new digital report mentioning your brand.
308+
CleanApp AI analyzed this issue to highlight potential legal and risk ranges connected to your brand presence.%s%s
309+
310+
AI analysis summary:
311+
- Title: %s
312+
- Description: %s
313+
- Type: Digital Issue
314+
315+
Open the Brand Dashboard to see the AI rationale, mapped areas, and supporting media:
304316
%s
305-
Note: This is a digital issue report. Physical metrics (litter/hazard probability) are not applicable.
306317
307318
To unsubscribe from these emails, please visit: %s?email=%s
308319
You can also reply to this email with "UNSUBSCRIBE" in the subject line.
309320
310321
Best regards,
311322
The CleanApp Team`,
323+
digitalSubject,
324+
preheader,
325+
heroReport,
326+
heroLocation,
312327
analysis.Title,
313328
analysis.Description,
314-
attachments,
329+
e.config.BrandDashboardURL,
315330
e.config.OptOutURL,
316331
recipient)
317-
} else {
318-
content = fmt.Sprintf(`Hello,
332+
}
333+
334+
attachments := ""
335+
if hasReport || hasMap {
336+
attachments = "\nThis email contains:\n"
337+
if hasReport {
338+
attachments += "- The report image\n"
339+
}
340+
if hasMap {
341+
attachments += "- A map showing the location\n"
342+
}
343+
attachments += "- AI analysis results\n"
344+
}
345+
346+
return fmt.Sprintf(`Hello,
319347
320348
You have received a new CleanApp report with analysis.
321349
@@ -334,29 +362,116 @@ You can also reply to this email with "UNSUBSCRIBE" in the subject line.
334362
335363
Best regards,
336364
The CleanApp Team`,
365+
analysis.Title,
366+
analysis.Description,
367+
analysis.LitterProbability*100,
368+
analysis.HazardProbability*100,
369+
analysis.SeverityLevel,
370+
attachments,
371+
e.config.OptOutURL,
372+
recipient)
373+
}
374+
375+
// getEmailHtmlWithAnalysis returns the HTML content for emails with analysis data
376+
func (e *EmailSender) getEmailHtmlWithAnalysis(recipient string, analysis *models.ReportAnalysis, hasReport, hasMap bool) string {
377+
isDigital := analysis.Classification == "digital"
378+
379+
if isDigital {
380+
subjectLine := "CleanApp alert: major new issue reported for your brand"
381+
preheader := "Someone just submitted a brand-related digital report. Review the AI analysis and risk ranges."
382+
383+
reportHero := ""
384+
if hasReport {
385+
reportHero = fmt.Sprintf(`
386+
<div class="hero-card">
387+
<div class="hero-label">Photo of report</div>
388+
<img src="cid:%s" alt="Report Image" />
389+
</div>`, reportImgCid)
390+
}
391+
392+
locationHero := ""
393+
if hasMap {
394+
locationHero = fmt.Sprintf(`
395+
<div class="hero-card">
396+
<div class="hero-label">Photo of location</div>
397+
<img src="cid:%s" alt="Location Map" />
398+
</div>`, mapImgCid)
399+
}
400+
401+
heroImages := ""
402+
if reportHero != "" || locationHero != "" {
403+
heroImages = fmt.Sprintf(`
404+
<div class="hero-grid">%s%s
405+
</div>`, reportHero, locationHero)
406+
}
407+
408+
return fmt.Sprintf(`<!DOCTYPE html>
409+
<html>
410+
<head>
411+
<meta charset="utf-8">
412+
<title>%s</title>
413+
<style>
414+
body { font-family: Arial, sans-serif; line-height: 1.6; color: #1f2937; background: #f7f7f8; margin: 0; padding: 0; }
415+
.preheader { display: none; visibility: hidden; opacity: 0; height: 0; width: 0; overflow: hidden; }
416+
.container { max-width: 720px; margin: 0 auto; padding: 24px; background: #ffffff; }
417+
.hero { background: linear-gradient(135deg, #0f766e, #14b8a6); color: #ffffff; padding: 28px; border-radius: 14px; box-shadow: 0 10px 30px rgba(0,0,0,0.12); }
418+
.eyebrow { text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; font-size: 12px; margin: 0 0 6px 0; opacity: 0.85; }
419+
h1 { margin: 0 0 10px 0; font-size: 26px; }
420+
.subhead { margin: 0 0 12px 0; font-size: 16px; opacity: 0.95; }
421+
.lede { margin: 0 0 18px 0; font-size: 15px; }
422+
.cta { display: inline-block; background: #ffffff; color: #0f172a; padding: 12px 18px; border-radius: 10px; text-decoration: none; font-weight: 700; box-shadow: 0 8px 20px rgba(0,0,0,0.12); }
423+
.card { margin-top: 24px; padding: 18px; border: 1px solid #e5e7eb; border-radius: 12px; background: #f8fafc; }
424+
.card h3 { margin-top: 0; color: #0f172a; }
425+
.card p { margin: 6px 0; }
426+
.card .note { margin-top: 12px; color: #475569; }
427+
.hero-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-top: 18px; }
428+
.hero-card { background: #0b766c0d; border: 1px solid #d1fae5; border-radius: 12px; padding: 12px; text-align: center; }
429+
.hero-label { font-weight: 700; color: #0f766e; margin-bottom: 10px; }
430+
.hero-card img { max-width: 100%%; border-radius: 10px; }
431+
.footer { margin-top: 24px; font-size: 13px; color: #6b7280; text-align: left; }
432+
.footer a { color: #0ea5e9; text-decoration: none; }
433+
</style>
434+
</head>
435+
<body>
436+
<div class="preheader">%s</div>
437+
<div class="container">
438+
<div class="hero">
439+
<p class="eyebrow">CleanApp alert</p>
440+
<h1>Major new issue reported for your brand</h1>
441+
<p class="subhead">Someone just submitted a brand-related digital report.</p>
442+
<p class="lede">CleanApp AI analyzed this issue to highlight potential legal and risk ranges connected to your brand presence.</p>
443+
<a class="cta" href="%s">Open brand dashboard</a>
444+
</div>
445+
446+
<div class="card">
447+
<h3>AI analysis summary</h3>
448+
<p><strong>Title:</strong> %s</p>
449+
<p><strong>Description:</strong> %s</p>
450+
<p><strong>Type:</strong> Digital Issue</p>
451+
<p class="note">Review the dashboard to see the AI rationale, mapped legal/risk ranges, and supporting media.</p>
452+
</div>%s
453+
454+
<div class="footer">
455+
<p>To unsubscribe from these emails, please <a href="%s?email=%s">click here</a>.</p>
456+
</div>
457+
</div>
458+
</body>
459+
</html>`,
460+
subjectLine,
461+
preheader,
462+
e.config.BrandDashboardURL,
337463
analysis.Title,
338464
analysis.Description,
339-
analysis.LitterProbability*100,
340-
analysis.HazardProbability*100,
341-
analysis.SeverityLevel,
342-
attachments,
465+
heroImages,
343466
e.config.OptOutURL,
344467
recipient)
345468
}
346469

347-
return content
348-
}
349-
350-
// getEmailHtmlWithAnalysis returns the HTML content for emails with analysis data
351-
func (e *EmailSender) getEmailHtmlWithAnalysis(recipient string, analysis *models.ReportAnalysis, hasReport, hasMap bool) string {
352470
// Calculate gauge colors based on values
353471
litterColor := e.getGaugeColor(analysis.LitterProbability)
354472
hazardColor := e.getGaugeColor(analysis.HazardProbability)
355473
severityColor := e.getSeverityGaugeColor(analysis.SeverityLevel)
356474

357-
// Determine if this is a digital report
358-
isDigital := analysis.Classification == "digital"
359-
360475
imagesSection := ""
361476
if hasReport {
362477
imagesSection += fmt.Sprintf(`
@@ -403,21 +518,21 @@ func (e *EmailSender) getEmailHtmlWithAnalysis(recipient string, analysis *model
403518
<h2>CleanApp Report Analysis</h2>
404519
<p>A new report has been analyzed and requires your attention.</p>
405520
</div>
406-
521+
407522
<div class="analysis-section">
408523
<h3>Report Details</h3>
409524
<p><strong>Title:</strong> %s</p>
410525
<p><strong>Description:</strong> %s</p>
411526
<p><strong>Type:</strong> %s</p>
412527
</div>
413-
528+
414529
%s
415-
530+
416531
<div class="images">%s
417532
</div>
418-
533+
419534
<p><em>Best regards,<br>The CleanApp Team</em></p>
420-
535+
421536
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; font-size: 0.9em; color: #666;">
422537
<p>To unsubscribe from these emails, please <a href="%s?email=%s" style="color: #007bff; text-decoration: none;">click here</a></p>
423538
</div>

0 commit comments

Comments
 (0)