[BE-SVC-004] be-svc: Implement email service

This commit is contained in:
senke 2025-12-24 16:10:11 +01:00
parent 64d764c16f
commit 1cf863a78b
2 changed files with 172 additions and 2 deletions

View file

@ -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",

View file

@ -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 := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Welcome to Veza</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<h1 style="color: #4CAF50;">Welcome to Veza, {{.Username}}!</h1>
<p>Thank you for joining Veza. We're excited to have you on board!</p>
<p>Get started by:</p>
<ul>
<li>Uploading your first track</li>
<li>Creating playlists</li>
<li>Discovering new music</li>
</ul>
<div style="text-align: center; margin: 30px 0;">
<a href="{{.BaseURL}}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
Get Started
</a>
</div>
<p style="margin-top: 30px; color: #666; font-size: 12px;">
If you have any questions, feel free to reach out to our support team.
</p>
</div>
</body>
</html>
`
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 := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Notification</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="text-align: center; margin-bottom: 20px;">
<span style="font-size: 48px;">{{.IconText}}</span>
</div>
<div style="background-color: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0;">
<p style="margin: 0;">{{.Message}}</p>
</div>
<div style="text-align: center; margin: 30px 0;">
<a href="{{.BaseURL}}" style="background-color: {{.IconColor}}; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
View on Veza
</a>
</div>
<p style="margin-top: 30px; color: #666; font-size: 12px;">
You can manage your notification preferences in your account settings.
</p>
</div>
</body>
</html>
`
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()
}