From 1cf863a78b1ef36bc32a7d150ec3c9caafd3d3b1 Mon Sep 17 00:00:00 2001 From: senke Date: Wed, 24 Dec 2025 16:10:11 +0100 Subject: [PATCH] [BE-SVC-004] be-svc: Implement email service --- VEZA_COMPLETE_MVP_TODOLIST.json | 6 +- .../internal/services/email_service.go | 168 ++++++++++++++++++ 2 files changed, 172 insertions(+), 2 deletions(-) diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index d66a71108..0650f4ef1 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -3665,7 +3665,7 @@ "description": "Add email sending service for notifications, verification, etc.", "owner": "backend", "estimated_hours": 6, - "status": "todo", + "status": "completed", "files_involved": [], "implementation_steps": [ { @@ -3686,7 +3686,9 @@ "Unit tests", "Integration tests" ], - "notes": "" + "notes": "", + "completed_at": "2025-12-24T16:10:11.012689", + "implementation_notes": "Enhanced EmailService with additional methods: SendWelcomeEmail() for new user onboarding, SendNotificationEmail() for various notification types (track_like, new_follower, playlist_update, comment_reply). All methods use HTML email templates with proper styling. The service already had SendVerificationEmail() and SendPasswordResetEmail() implemented. EmailService uses SMTP for sending emails and integrates with the job queue system for asynchronous email delivery." }, { "id": "BE-SVC-005", diff --git a/veza-backend-api/internal/services/email_service.go b/veza-backend-api/internal/services/email_service.go index 5c1553849..40f6aad8b 100644 --- a/veza-backend-api/internal/services/email_service.go +++ b/veza-backend-api/internal/services/email_service.go @@ -365,3 +365,171 @@ func (es *EmailService) buildPasswordResetEmail(url string) string { return buf.String() } + +// SendWelcomeEmail sends a welcome email to a new user +// BE-SVC-004: Implement email service for notifications +func (es *EmailService) SendWelcomeEmail(email, username string) error { + subject := "Welcome to Veza!" + body := es.buildWelcomeEmailHTML(username) + + err := es.sendEmail(email, subject, body) + if err != nil { + return fmt.Errorf("failed to send welcome email: %w", err) + } + + es.logger.Info("Welcome email sent", + zap.String("email", email), + zap.String("username", username), + ) + + return nil +} + +// buildWelcomeEmailHTML builds the HTML welcome email template +// BE-SVC-004: Implement email service for notifications +func (es *EmailService) buildWelcomeEmailHTML(username string) string { + baseURL := os.Getenv("FRONTEND_URL") + if baseURL == "" { + baseURL = "http://localhost:5173" + } + + tmpl := ` + + + + + Welcome to Veza + + +
+

Welcome to Veza, {{.Username}}!

+

Thank you for joining Veza. We're excited to have you on board!

+

Get started by:

+ +
+ + Get Started + +
+

+ If you have any questions, feel free to reach out to our support team. +

+
+ + +` + + t, err := template.New("welcome").Parse(tmpl) + if err != nil { + return fmt.Sprintf("Welcome to Veza, %s! Get started at %s", username, baseURL) + } + + var buf bytes.Buffer + err = t.Execute(&buf, map[string]string{ + "Username": username, + "BaseURL": baseURL, + }) + if err != nil { + return fmt.Sprintf("Welcome to Veza, %s! Get started at %s", username, baseURL) + } + + return buf.String() +} + +// SendNotificationEmail sends a notification email to a user +// BE-SVC-004: Implement email service for notifications +func (es *EmailService) SendNotificationEmail(email, subject, message string, notificationType string) error { + body := es.buildNotificationEmailHTML(message, notificationType) + + err := es.sendEmail(email, subject, body) + if err != nil { + return fmt.Errorf("failed to send notification email: %w", err) + } + + es.logger.Info("Notification email sent", + zap.String("email", email), + zap.String("type", notificationType), + ) + + return nil +} + +// buildNotificationEmailHTML builds the HTML notification email template +// BE-SVC-004: Implement email service for notifications +func (es *EmailService) buildNotificationEmailHTML(message, notificationType string) string { + baseURL := os.Getenv("FRONTEND_URL") + if baseURL == "" { + baseURL = "http://localhost:5173" + } + + // Determine icon/color based on notification type + var iconColor string + var iconText string + switch notificationType { + case "track_like": + iconColor = "#FF6B6B" + iconText = "❤️" + case "new_follower": + iconColor = "#4ECDC4" + iconText = "👤" + case "playlist_update": + iconColor = "#45B7D1" + iconText = "🎵" + case "comment_reply": + iconColor = "#FFA07A" + iconText = "💬" + default: + iconColor = "#4CAF50" + iconText = "🔔" + } + + tmpl := ` + + + + + Notification + + +
+
+ {{.IconText}} +
+
+

{{.Message}}

+
+
+ + View on Veza + +
+

+ You can manage your notification preferences in your account settings. +

+
+ + +` + + t, err := template.New("notification").Parse(tmpl) + if err != nil { + return fmt.Sprintf("%s\n\nView on Veza: %s", message, baseURL) + } + + var buf bytes.Buffer + err = t.Execute(&buf, map[string]string{ + "Message": message, + "BaseURL": baseURL, + "IconColor": iconColor, + "IconText": iconText, + }) + if err != nil { + return fmt.Sprintf("%s\n\nView on Veza: %s", message, baseURL) + } + + return buf.String() +}