Files
warmbly/internal/tasks/user_email_task.go
T

143 lines
3.6 KiB
Go

package tasks
import (
"context"
"time"
"github.com/getsentry/sentry-go"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"github.com/warmbly/warmbly/internal/errx"
"github.com/warmbly/warmbly/internal/tasks/proto"
)
func (s *tasksService) HandleUserEmailTask(task *proto.ProcessTask) *errx.Error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
// STEP 1: Parse task ID
taskID, err := uuid.Parse(task.TaskId)
if err != nil {
sentry.CaptureException(err)
return errx.New(errx.BadRequest, "invalid task ID")
}
executionKey := "user-email:" + taskID.String()
executionStatus := "failed"
if s.advanced != nil {
duplicate, xerr := s.advanced.StartTaskExecution(ctx, taskID, executionKey, map[string]interface{}{
"task_type": "user_email",
})
if xerr != nil {
return xerr
}
if duplicate {
return nil
}
defer func() {
_ = s.advanced.CompleteTaskExecution(ctx, taskID, executionKey, executionStatus, map[string]interface{}{
"task_type": "user_email",
})
}()
}
// STEP 2: Load task record
taskRecord, err := s.taskRepo.GetTask(ctx, taskID)
if err != nil {
sentry.CaptureException(err)
return errx.InternalError()
}
if taskRecord == nil {
return errx.ErrNotFound
}
// STEP 3: Load email_task record (with new columns)
emailTask, err := s.taskRepo.GetEmailTask(ctx, taskID)
if err != nil {
sentry.CaptureException(err)
return errx.InternalError()
}
if emailTask == nil {
return errx.ErrNotFound
}
// STEP 4: Load email account
account, xerr := s.emailRepo.GetByID(ctx, taskRecord.EmailAccountID)
if xerr != nil {
return xerr
}
// STEP 5: Mark task as active (with advisory lock)
if err := s.taskRepo.UpdateTaskStatusWithLock(ctx, taskID, "active"); err != nil {
sentry.CaptureException(err)
return errx.InternalError()
}
// STEP 6: Add signature if SignatureSync
bodyHTML := emailTask.BodyHTML
bodyPlain := emailTask.BodyPlain
if account.SignatureSync {
if bodyHTML != "" {
bodyHTML = AddSignature(bodyHTML, account.SignatureHTML, true)
}
if bodyPlain != "" {
bodyPlain = AddSignature(bodyPlain, account.SignaturePlain, false)
}
}
// STEP 7: Generate Message-ID
messageID := generateMessageID(account.Email)
// STEP 8: Build InReplyTo string
var inReplyTo string
if len(emailTask.InReplyTo) > 0 {
inReplyTo = emailTask.InReplyTo[0]
}
// STEP 9: Build EmailMessage and send via worker
emailMsg := EmailMessage{
From: account.Email,
To: emailTask.To,
CC: emailTask.CC,
BCC: emailTask.BCC,
Subject: emailTask.Subject,
BodyHTML: bodyHTML,
BodyPlain: bodyPlain,
InReplyTo: inReplyTo,
MessageID: messageID,
IsWarmup: false,
Tracking: nil,
}
if err := s.emailSender.Send(ctx, taskID, emailMsg, *account); err != nil {
s.taskRepo.RecordTaskFailure(ctx, taskID, "Send failed", err.Error())
if s.advanced != nil {
_ = s.advanced.CaptureTaskDeadLetter(ctx, taskID, "user_email", map[string]interface{}{
"account_id": account.ID.String(),
"to": emailTask.To,
}, err.Error(), 1)
_ = s.taskRepo.UpdateTaskStatus(ctx, taskID, "dead_lettered")
}
return nil
}
// STEP 10: Update task record
taskRecord.MessageID = messageID
taskRecord.Status = "completed"
// STEP 11: Mark task completed (with advisory lock)
if err := s.taskRepo.UpdateTaskStatusWithLock(ctx, taskID, "completed"); err != nil {
sentry.CaptureException(err)
return errx.InternalError()
}
// STEP 12: Publish events
log.Info().Str("task_id", task.TaskId).Str("account_id", account.ID.String()).Msg("user email sent")
executionStatus = "completed"
return nil
}