diff --git a/internal/router/router.go b/internal/router/router.go
index 0166445..cd9b06d 100644
--- a/internal/router/router.go
+++ b/internal/router/router.go
@@ -75,8 +75,9 @@ func SetupRouter(deps *Dependencies) *gin.Engine {
documentService := services.NewDocumentService(documentRepo, residenceRepo)
notificationService := services.NewNotificationService(notificationRepo, gorushClient)
- // Wire up notification service to task service (for task completion notifications)
+ // Wire up notification and email services to task service (for task completion notifications)
taskService.SetNotificationService(notificationService)
+ taskService.SetEmailService(deps.EmailService)
subscriptionService := services.NewSubscriptionService(subscriptionRepo, residenceRepo, taskRepo, contractorRepo, documentRepo)
// Initialize middleware
diff --git a/internal/services/email_service.go b/internal/services/email_service.go
index 24f55d0..ffd6e0f 100644
--- a/internal/services/email_service.go
+++ b/internal/services/email_service.go
@@ -280,6 +280,68 @@ The MyCrib Team
return s.SendEmail(to, subject, htmlBody, textBody)
}
+// SendTaskCompletedEmail sends an email notification when a task is completed
+func (s *EmailService) SendTaskCompletedEmail(to, recipientName, taskTitle, completedByName, residenceName string) error {
+ subject := fmt.Sprintf("MyCrib - Task Completed: %s", taskTitle)
+
+ name := recipientName
+ if name == "" {
+ name = "there"
+ }
+
+ htmlBody := fmt.Sprintf(`
+
+
+
+
+
+
+
+
+
+
Hi %s,
+
A task has been completed at %s:
+
+
%s
+
Completed by: %s
Completed on: %s
+
+
Best regards,
The MyCrib Team
+
+
+
+
+`, name, residenceName, taskTitle, completedByName, time.Now().UTC().Format("January 2, 2006 at 3:04 PM"), time.Now().Year())
+
+ textBody := fmt.Sprintf(`
+Task Completed!
+
+Hi %s,
+
+A task has been completed at %s:
+
+Task: %s
+Completed by: %s
+Completed on: %s
+
+Best regards,
+The MyCrib Team
+`, name, residenceName, taskTitle, completedByName, time.Now().UTC().Format("January 2, 2006 at 3:04 PM"))
+
+ return s.SendEmail(to, subject, htmlBody, textBody)
+}
+
// EmailTemplate represents an email template
type EmailTemplate struct {
name string
diff --git a/internal/services/task_service.go b/internal/services/task_service.go
index a3d7723..2a7828b 100644
--- a/internal/services/task_service.go
+++ b/internal/services/task_service.go
@@ -29,6 +29,7 @@ type TaskService struct {
taskRepo *repositories.TaskRepository
residenceRepo *repositories.ResidenceRepository
notificationService *NotificationService
+ emailService *EmailService
}
// NewTaskService creates a new task service
@@ -44,6 +45,11 @@ func (s *TaskService) SetNotificationService(ns *NotificationService) {
s.notificationService = ns
}
+// SetEmailService sets the email service
+func (s *TaskService) SetEmailService(es *EmailService) {
+ s.emailService = es
+}
+
// === Task CRUD ===
// GetTask gets a task by ID with access check
@@ -485,11 +491,6 @@ func (s *TaskService) CreateCompletion(req *requests.CreateTaskCompletionRequest
// sendTaskCompletedNotification sends notifications when a task is completed
func (s *TaskService) sendTaskCompletedNotification(task *models.Task, completion *models.TaskCompletion) {
- if s.notificationService == nil {
- log.Debug().Msg("Notification service not configured, skipping task completion notification")
- return
- }
-
// Get all users with access to this residence
users, err := s.residenceRepo.GetResidenceUsers(task.ResidenceID)
if err != nil {
@@ -497,6 +498,13 @@ func (s *TaskService) sendTaskCompletedNotification(task *models.Task, completio
return
}
+ // Get residence name
+ residence, err := s.residenceRepo.FindByIDSimple(task.ResidenceID)
+ residenceName := "your property"
+ if err == nil && residence != nil {
+ residenceName = residence.Name
+ }
+
completedByName := "Someone"
if completion.CompletedBy.ID > 0 {
completedByName = completion.CompletedBy.GetFullName()
@@ -517,19 +525,39 @@ func (s *TaskService) sendTaskCompletedNotification(task *models.Task, completio
continue // Don't notify the person who completed it
}
- go func(userID uint) {
- ctx := context.Background()
- if err := s.notificationService.CreateAndSendNotification(
- ctx,
- userID,
- models.NotificationTaskCompleted,
- title,
- body,
- data,
- ); err != nil {
- log.Error().Err(err).Uint("user_id", userID).Uint("task_id", task.ID).Msg("Failed to send task completion notification")
- }
- }(user.ID)
+ // Send push notification
+ if s.notificationService != nil {
+ go func(userID uint) {
+ ctx := context.Background()
+ if err := s.notificationService.CreateAndSendNotification(
+ ctx,
+ userID,
+ models.NotificationTaskCompleted,
+ title,
+ body,
+ data,
+ ); err != nil {
+ log.Error().Err(err).Uint("user_id", userID).Uint("task_id", task.ID).Msg("Failed to send task completion push notification")
+ }
+ }(user.ID)
+ }
+
+ // Send email notification
+ if s.emailService != nil && user.Email != "" {
+ go func(u models.User) {
+ if err := s.emailService.SendTaskCompletedEmail(
+ u.Email,
+ u.GetFullName(),
+ task.Title,
+ completedByName,
+ residenceName,
+ ); err != nil {
+ log.Error().Err(err).Str("email", u.Email).Uint("task_id", task.ID).Msg("Failed to send task completion email")
+ } else {
+ log.Info().Str("email", u.Email).Uint("task_id", task.ID).Msg("Task completion email sent")
+ }
+ }(user)
+ }
}
}