func (s *EmailServer) queueToDb() { var em email.Email wait, _ := time.ParseDuration("5m") for { d, err := s.queue.PopWait(wait) panik.On(err) if len(d) > 0 { // read from queue //err = ds.LoadStruct(&em, d) err = ds.Load(&em, d) if err != nil { log.Error("Error reading email from queue", "email", em, "error", err) panik.On(err) } // save to db m := Message{ Data: string(d), Priority: em.Priority, CreatedAt: time.Now().Unix(), SendAfter: em.SendAfter, } err = s.dbo.Create(&m).Error if err != nil { // on error, put back in queue s.queue.Push(d) panik.On(err) } } } }
func NewEmailServer() *EmailServer { es := EmailServer{ dbo: orm.ReadConfig("email.server.database"), auth: smtp.PlainAuth( conf.String("", "email.server.smtp.auth", "identity"), conf.String("", "email.server.smtp.auth", "username"), conf.String("", "email.server.smtp.auth", "password"), conf.String("", "email.server.smtp.auth", "host"), ), } // testing es.dbo.LogMode(true) es.address = conf.String("", "email.server.smtp", "host") if conf.Exists("email.server.smtp", "port") { es.address = fmt.Sprintf("%s:%d", es.address, conf.Int(0, "email.server.smtp", "port")) } if conf.Exists("email.queue.default") { es.queue = que.NewQueue("email.queue.default") } // create relational tables if missing if !es.dbo.HasTable(Message{}) { panik.On(es.dbo.AutoMigrate(&Message{}).Error) fmt.Println("[message] table created.") } return &es }
func (s *EmailServer) Run() { go s.queueToDb() batch := conf.Int(3, "email", "server", "batch") maxFails := conf.Int(3, "email", "server", "max-fails") maxTime := conf.String("1h", "email", "server", "max-time") dur, err := time.ParseDuration(maxTime) panik.On(err) maxTimeSec := int(dur.Seconds()) now := time.Now().Unix() var upd map[string]interface{} var msgs []Message sql := ` sent = 0 and num_fails < ? and ((send_after is null and ? - created_at between 0 and ?) or (send_after is not null and ? - send_after between 0 and ?))` for { s.dbo.Where(sql, maxFails, now, maxTimeSec, now, maxTimeSec). Order("priority desc, created_at asc"). Limit(batch). Find(&msgs) if len(msgs) == 0 { time.Sleep(5 * time.Second) } else { for i := 0; i < len(msgs); i++ { err := s.dispatchMessage(&msgs[i]) now = time.Now().Unix() if err == nil { upd = map[string]interface{}{ "sent": 1, "sent_at": &now, "updated_at": &now, } } else { upd = map[string]interface{}{ "num_fails": msgs[i].NumFails + 1, "last_failed_at": &now, "failure": err.Error(), "updated_at": &now, } } edb := s.dbo.Model(Message{}).Where("id = ?", msgs[i].Id).Updates(upd).Error panik.On2(edb, func() { log.Error("Failed to update message table", "email-err", err, "db-err", edb) }) } } } }
func (e *Email) Enque() { if mails == nil { // initialization startLoop() } go func() { d, err := ds.ToBytes(e, false) panik.On(err) mails <- d }() }
func startLoop() { if mails != nil { return } mails = make(chan []byte, conf.Int(size, "email", "buffer")) panik.If(Queue == nil, "Queue is not initialized") go func() { for { select { case mb := <-mails: err := Queue.Push(mb) panik.On(err) } } }() }
func (e *Email) SendLater(after string) { d, err := time.ParseDuration(after) panik.On(err) e.SendAt(time.Now().Add(d)) }