func (et *EmailTracker) Send( ctx scope.Context, js jobs.JobService, templater *templates.Templater, deliverer emails.Deliverer, account proto.Account, to, templateName string, data interface{}) ( *emails.EmailRef, error) { if to == "" { to, _ = account.Email() } sf, err := snowflake.New() if err != nil { return nil, err } msgID := fmt.Sprintf("<%s@%s>", sf, deliverer.LocalName()) ref, err := emails.NewEmail(templater, msgID, to, templateName, data) if err != nil { return nil, err } ref.AccountID = account.ID() jq, err := js.GetQueue(ctx, jobs.EmailQueue) if err != nil { return nil, err } payload := &jobs.EmailJob{ AccountID: account.ID(), EmailID: ref.ID, } job, err := jq.AddAndClaim(ctx, jobs.EmailJobType, payload, "immediate", jobs.EmailJobOptions...) if err != nil { return nil, err } ref.JobID = job.ID et.m.Lock() if et.emailsByAccount == nil { et.emailsByAccount = map[snowflake.Snowflake][]*emails.EmailRef{} } et.emailsByAccount[account.ID()] = append(et.emailsByAccount[account.ID()], ref) et.m.Unlock() child := ctx.Fork() child.WaitGroup().Add(1) go job.Exec(child, func(ctx scope.Context) error { defer ctx.WaitGroup().Done() logging.Logger(ctx).Printf("delivering to %s\n", to) if err := deliverer.Deliver(ctx, ref); err != nil { return err } return nil }) return ref, nil }
func (et *EmailTracker) Send( ctx scope.Context, js jobs.JobService, templater *templates.Templater, deliverer emails.Deliverer, account proto.Account, to, templateName string, data interface{}) ( *emails.EmailRef, error) { if to == "" { to, _ = account.Email() } // choose a Message-ID sf, err := snowflake.New() if err != nil { return nil, err } domain := "heim" if deliverer != nil { domain = deliverer.LocalName() } msgID := fmt.Sprintf("<%s@%s>", sf, domain) // construct the email ref, err := emails.NewEmail(templater, msgID, to, templateName, data) if err != nil { return nil, err } ref.AccountID = account.ID() // get underlying JobQueue so we can add-and-claim in the same transaction as the email insert abstractQueue, err := js.GetQueue(ctx, jobs.EmailQueue) if err != nil { return nil, err } jq := abstractQueue.(*JobQueueBinding) t, err := et.Backend.DbMap.Begin() if err != nil { return nil, err } // insert job first, so we know what JobID to associate with the email when we insert it payload := &jobs.EmailJob{ AccountID: account.ID(), EmailID: ref.ID, } job, err := jq.addAndClaim(ctx, t, jobs.EmailJobType, payload, "immediate", jobs.EmailJobOptions...) if err != nil { rollback(ctx, t) return nil, err } ref.JobID = job.ID // insert the email var email Email email.FromBackend(ref) if err := t.Insert(&email); err != nil { rollback(ctx, t) return nil, err } // finalize and spin off first delivery attempt if err := t.Commit(); err != nil { return nil, err } child := ctx.Fork() child.WaitGroup().Add(1) go job.Exec(child, func(ctx scope.Context) error { defer ctx.WaitGroup().Done() logging.Logger(ctx).Printf("delivering to %s\n", to) if deliverer == nil { return fmt.Errorf("deliverer not configured") } if err := deliverer.Deliver(ctx, ref); err != nil { return err } if _, err := et.Backend.DbMap.Exec("UPDATE email SET delivered = $2 WHERE id = $1", ref.ID, ref.Delivered); err != nil { // Even if we fail to mark the email as delivered, don't return an // error so the job still gets completed. We wouldn't want to spam // someone just because of a DB issue. logging.Logger(ctx).Printf("error marking email %s/%s as delivered: %s", account.ID(), ref.ID, err) } return nil }) return ref, nil }