mirror of
https://github.com/ghostersk/gowebmail.git
synced 2026-06-17 07:59:38 +01:00
updated outlook account sync
This commit is contained in:
@@ -6,12 +6,12 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ghostersk/gowebmail/internal/logger"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"golang.org/x/oauth2/microsoft"
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
// GmailScopes are the OAuth2 scopes required for full Gmail access.
|
||||
var GmailScopes = []string{
|
||||
"https://mail.google.com/", // Full IMAP+SMTP access
|
||||
"https://mail.google.com/", // Full IMAP+SMTP access
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func ExchangeForIMAPToken(ctx context.Context, clientID, clientSecret, tenantID,
|
||||
preview = preview[:30] + "..."
|
||||
}
|
||||
parts := strings.Count(result.AccessToken, ".") + 1
|
||||
log.Printf("[oauth:outlook:exchange] got token with %d parts: %s (scope=%s)",
|
||||
logger.Debug("[oauth:outlook:exchange] got token with %d parts: %s (scope=%s)",
|
||||
parts, preview, params.Get("scope"))
|
||||
|
||||
expiry := time.Now().Add(time.Duration(result.ExpiresIn) * time.Second)
|
||||
@@ -162,10 +162,10 @@ func ExchangeForIMAPToken(ctx context.Context, clientID, clientSecret, tenantID,
|
||||
// MicrosoftUserInfo holds user info extracted from the Microsoft ID token.
|
||||
type MicrosoftUserInfo struct {
|
||||
ID string `json:"id"`
|
||||
DisplayName string `json:"displayName"` // Graph field
|
||||
Name string `json:"name"` // ID token claim
|
||||
DisplayName string `json:"displayName"` // Graph field
|
||||
Name string `json:"name"` // ID token claim
|
||||
Mail string `json:"mail"`
|
||||
EmailClaim string `json:"email"` // ID token claim
|
||||
EmailClaim string `json:"email"` // ID token claim
|
||||
UserPrincipalName string `json:"userPrincipalName"`
|
||||
PreferredUsername string `json:"preferred_username"` // ID token claim
|
||||
}
|
||||
|
||||
@@ -1235,6 +1235,14 @@ func (d *DB) UpdateMessageBody(messageID int64, bodyText, bodyHTML string) {
|
||||
bodyTextEnc, bodyHTMLEnc, messageID)
|
||||
}
|
||||
|
||||
// GetNewestMessageDate returns the date of the most recent message in a folder.
|
||||
// Returns zero time if the folder is empty.
|
||||
func (d *DB) GetNewestMessageDate(folderID int64) time.Time {
|
||||
var t time.Time
|
||||
d.sql.QueryRow(`SELECT MAX(date) FROM messages WHERE folder_id=?`, folderID).Scan(&t)
|
||||
return t
|
||||
}
|
||||
|
||||
func (d *DB) ToggleMessageStar(messageID, userID int64) (bool, error) {
|
||||
var current bool
|
||||
err := d.sql.QueryRow(`
|
||||
|
||||
+10
-9
@@ -19,6 +19,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ghostersk/gowebmail/internal/logger"
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/client"
|
||||
|
||||
@@ -64,9 +65,9 @@ func (x *xoauth2Client) Next(challenge []byte) ([]byte, error) {
|
||||
if len(challenge) > 0 {
|
||||
// Decode and log the error from Microsoft so it appears in server logs
|
||||
if dec, err := base64.StdEncoding.DecodeString(string(challenge)); err == nil {
|
||||
log.Printf("[imap:xoauth2] server error for %s: %s", x.user, string(dec))
|
||||
logger.Debug("[imap:xoauth2] server error for %s: %s", x.user, string(dec))
|
||||
} else {
|
||||
log.Printf("[imap:xoauth2] server challenge for %s: %s", x.user, string(challenge))
|
||||
logger.Debug("[imap:xoauth2] server challenge for %s: %s", x.user, string(challenge))
|
||||
}
|
||||
// Send empty response to let the server send the final error
|
||||
return []byte("\x01"), nil
|
||||
@@ -142,18 +143,18 @@ func Connect(ctx context.Context, account *gomailModels.EmailAccount) (*Client,
|
||||
Upn string `json:"upn"`
|
||||
}
|
||||
if json.Unmarshal(payload, &claims) == nil {
|
||||
log.Printf("[imap:connect] %s aud=%v scp=%q token=%s",
|
||||
logger.Debug("[imap:connect] %s aud=%v scp=%q token=%s",
|
||||
account.EmailAddress, claims.Aud, claims.Scp, tokenPreview)
|
||||
} else {
|
||||
log.Printf("[imap:connect] %s raw claims: %s token=%s",
|
||||
logger.Debug("[imap:connect] %s raw claims: %s token=%s",
|
||||
account.EmailAddress, string(payload), tokenPreview)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[imap:connect] %s opaque token (not JWT): %s",
|
||||
logger.Debug("[imap:connect] %s opaque token (not JWT): %s",
|
||||
account.EmailAddress, tokenPreview)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[imap:connect] %s token has %d parts (not JWT): %s",
|
||||
logger.Debug("[imap:connect] %s token has %d parts (not JWT): %s",
|
||||
account.EmailAddress, len(strings.Split(account.AccessToken, ".")), tokenPreview)
|
||||
}
|
||||
sasl := &xoauth2Client{user: account.EmailAddress, token: account.AccessToken}
|
||||
@@ -900,7 +901,7 @@ func SendMessageFull(ctx context.Context, account *gomailModels.EmailAccount, re
|
||||
rawMsg := buf.Bytes()
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
log.Printf("[SMTP] dialing %s for account %s", addr, account.EmailAddress)
|
||||
logger.Debug("[SMTP] dialing %s for account %s", addr, account.EmailAddress)
|
||||
|
||||
var c *smtp.Client
|
||||
var err error
|
||||
@@ -941,7 +942,7 @@ func SendMessageFull(ctx context.Context, account *gomailModels.EmailAccount, re
|
||||
if err := authSMTP(c, account, host); err != nil {
|
||||
return fmt.Errorf("SMTP auth failed for %s: %w", account.EmailAddress, err)
|
||||
}
|
||||
log.Printf("[SMTP] auth OK")
|
||||
logger.Debug("[SMTP] auth OK")
|
||||
|
||||
if err := c.Mail(account.EmailAddress); err != nil {
|
||||
return fmt.Errorf("SMTP MAIL FROM <%s>: %w", account.EmailAddress, err)
|
||||
@@ -971,7 +972,7 @@ func SendMessageFull(ctx context.Context, account *gomailModels.EmailAccount, re
|
||||
// DATA close is where the server accepts or rejects the message
|
||||
return fmt.Errorf("SMTP server rejected message: %w", err)
|
||||
}
|
||||
log.Printf("[SMTP] message accepted by server")
|
||||
logger.Debug("[SMTP] message accepted by server")
|
||||
_ = c.Quit()
|
||||
|
||||
// Append to Sent folder via IMAP (best-effort, don't fail the send)
|
||||
|
||||
+41
-12
@@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -198,8 +199,10 @@ type messagesResp struct {
|
||||
func (c *Client) ListMessages(ctx context.Context, folderID string, since time.Time, maxResults int) ([]GraphMessage, error) {
|
||||
filter := ""
|
||||
if !since.IsZero() {
|
||||
filter = "&$filter=receivedDateTime+gt+" +
|
||||
url.QueryEscape(since.UTC().Format("2006-01-02T15:04:05Z"))
|
||||
// OData filter: receivedDateTime gt 2006-01-02T15:04:05Z
|
||||
// Use strings.ReplaceAll to keep colons unencoded — Graph accepts this form
|
||||
dateStr := since.UTC().Format("2006-01-02T15:04:05Z")
|
||||
filter = "&$filter=receivedDateTime gt " + url.PathEscape(dateStr)
|
||||
}
|
||||
top := 50
|
||||
if maxResults > 0 && maxResults < top {
|
||||
@@ -322,25 +325,51 @@ func WellKnownToFolderType(wk string) string {
|
||||
|
||||
// ---- Send mail ----
|
||||
|
||||
// stripHTML does a minimal HTML→plain-text conversion for the text/plain fallback.
|
||||
// Spam filters score HTML-only email negatively; sending both parts improves deliverability.
|
||||
func stripHTML(s string) string {
|
||||
s = regexp.MustCompile(`(?i)<br\s*/?>|</p>|</div>|</li>|</tr>`).ReplaceAllString(s, "\n")
|
||||
s = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(s, "")
|
||||
s = strings.NewReplacer("&", "&", "<", "<", ">", ">", """, `"`, "'", "'", " ", " ").Replace(s)
|
||||
s = regexp.MustCompile(`\n{3,}`).ReplaceAllString(s, "\n\n")
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
// SendMail sends an email via Graph API POST /me/sendMail.
|
||||
// Sets both HTML and plain-text body to improve deliverability (spam filters
|
||||
// penalise HTML-only messages with no text/plain alternative).
|
||||
func (c *Client) SendMail(ctx context.Context, req *models.ComposeRequest) error {
|
||||
contentType := "HTML"
|
||||
body := req.BodyHTML
|
||||
if body == "" {
|
||||
contentType = "Text"
|
||||
body = req.BodyText
|
||||
// Build body: always provide both HTML and plain text for better deliverability
|
||||
body := map[string]string{
|
||||
"contentType": "HTML",
|
||||
"content": req.BodyHTML,
|
||||
}
|
||||
if req.BodyHTML == "" {
|
||||
body["contentType"] = "Text"
|
||||
body["content"] = req.BodyText
|
||||
}
|
||||
|
||||
// Set explicit from with display name
|
||||
var fromField interface{}
|
||||
if c.account.DisplayName != "" {
|
||||
fromField = map[string]interface{}{
|
||||
"emailAddress": map[string]string{
|
||||
"address": c.account.EmailAddress,
|
||||
"name": c.account.DisplayName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
msg := map[string]interface{}{
|
||||
"subject": req.Subject,
|
||||
"body": map[string]string{
|
||||
"contentType": contentType,
|
||||
"content": body,
|
||||
},
|
||||
"subject": req.Subject,
|
||||
"body": body,
|
||||
"toRecipients": graphRecipients(req.To),
|
||||
"ccRecipients": graphRecipients(req.CC),
|
||||
"bccRecipients": graphRecipients(req.BCC),
|
||||
}
|
||||
if fromField != nil {
|
||||
msg["from"] = fromField
|
||||
}
|
||||
|
||||
if len(req.Attachments) > 0 {
|
||||
var atts []map[string]interface{}
|
||||
|
||||
@@ -844,6 +844,11 @@ func (h *APIHandler) handleSend(w http.ResponseWriter, r *http.Request, mode str
|
||||
h.writeError(w, http.StatusBadGateway, err.Error())
|
||||
return
|
||||
}
|
||||
// Delay slightly so Microsoft has time to save to Sent Items before we sync
|
||||
go func() {
|
||||
time.Sleep(3 * time.Second)
|
||||
h.syncer.TriggerAccountSync(account.ID)
|
||||
}()
|
||||
h.writeJSON(w, map[string]bool{"ok": true})
|
||||
return
|
||||
}
|
||||
@@ -856,6 +861,8 @@ func (h *APIHandler) handleSend(w http.ResponseWriter, r *http.Request, mode str
|
||||
h.writeError(w, http.StatusBadGateway, err.Error())
|
||||
return
|
||||
}
|
||||
// Trigger immediate sync so the sent message appears in Sent Items
|
||||
h.syncer.TriggerAccountSync(account.ID)
|
||||
h.writeJSON(w, map[string]bool{"ok": true})
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ghostersk/gowebmail/internal/logger"
|
||||
"github.com/ghostersk/gowebmail/config"
|
||||
goauth "github.com/ghostersk/gowebmail/internal/auth"
|
||||
"github.com/ghostersk/gowebmail/internal/crypto"
|
||||
@@ -430,7 +431,7 @@ h2{color:#f87171} a{color:#6b8afd}</style></head><body>
|
||||
http.Redirect(w, r, "/?error=userinfo_failed", http.StatusFound)
|
||||
return
|
||||
}
|
||||
log.Printf("[oauth:outlook] auth successful for %s, getting IMAP token...", userInfo.Email())
|
||||
logger.Debug("[oauth:outlook] auth successful for %s, getting IMAP token...", userInfo.Email())
|
||||
|
||||
// Exchange initial token for one scoped to https://outlook.office.com
|
||||
// so IMAP auth succeeds (aud must be outlook.office.com not graph/live)
|
||||
@@ -440,10 +441,10 @@ h2{color:#f87171} a{color:#6b8afd}</style></head><body>
|
||||
h.cfg.MicrosoftTenantID, token.RefreshToken,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("[oauth:outlook] IMAP token exchange failed: %v — falling back to initial token", err)
|
||||
logger.Debug("[oauth:outlook] IMAP token exchange failed: %v — falling back to initial token", err)
|
||||
imapToken = token
|
||||
} else {
|
||||
log.Printf("[oauth:outlook] IMAP token obtained, aud should be https://outlook.office.com")
|
||||
logger.Debug("[oauth:outlook] IMAP token obtained, aud should be https://outlook.office.com")
|
||||
if imapToken.RefreshToken == "" {
|
||||
imapToken.RefreshToken = token.RefreshToken
|
||||
}
|
||||
@@ -703,7 +704,7 @@ Mail.ReadWrite, Mail.Send, User.Read, openid, email, offline_access</p>
|
||||
|
||||
// Verify it's a JWT (Graph token for personal accounts should be a JWT)
|
||||
tokenParts := len(strings.Split(token.AccessToken, "."))
|
||||
log.Printf("[oauth:outlook-personal] auth successful for %s, token parts: %d",
|
||||
logger.Debug("[oauth:outlook-personal] auth successful for %s, token parts: %d",
|
||||
userInfo.Email(), tokenParts)
|
||||
|
||||
accounts, _ := h.db.ListAccountsByUser(userID)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Package logger provides a conditional debug logger controlled by config.Debug.
|
||||
package logger
|
||||
|
||||
import "log"
|
||||
|
||||
var debugEnabled bool
|
||||
|
||||
// Init sets whether debug logging is active. Call once at startup.
|
||||
func Init(debug bool) {
|
||||
debugEnabled = debug
|
||||
if debug {
|
||||
log.Println("[logger] debug logging enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// Debug logs a message only when debug mode is on.
|
||||
func Debug(format string, args ...interface{}) {
|
||||
if debugEnabled {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled returns true if debug logging is on.
|
||||
func IsEnabled() bool { return debugEnabled }
|
||||
+56
-17
@@ -13,6 +13,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ghostersk/gowebmail/internal/logger"
|
||||
"github.com/ghostersk/gowebmail/config"
|
||||
"github.com/ghostersk/gowebmail/internal/auth"
|
||||
"github.com/ghostersk/gowebmail/internal/db"
|
||||
@@ -427,7 +428,7 @@ func (s *Scheduler) deltaSync(account *models.EmailAccount) {
|
||||
|
||||
s.db.UpdateAccountLastSync(account.ID)
|
||||
if totalNew > 0 {
|
||||
log.Printf("[sync:%s] %d new messages", account.EmailAddress, totalNew)
|
||||
logger.Debug("[sync:%s] %d new messages", account.EmailAddress, totalNew)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +454,7 @@ func (s *Scheduler) syncInbox(account *models.EmailAccount) {
|
||||
return
|
||||
}
|
||||
if n > 0 {
|
||||
log.Printf("[idle:%s] %d new messages in INBOX", account.EmailAddress, n)
|
||||
logger.Debug("[idle:%s] %d new messages in INBOX", account.EmailAddress, n)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,6 +537,10 @@ func (s *Scheduler) syncFolder(c *email.Client, account *models.EmailAccount, db
|
||||
// Applies queued IMAP write operations (delete/move/flag) with retry logic.
|
||||
|
||||
func (s *Scheduler) drainPendingOps(account *models.EmailAccount) {
|
||||
// Graph accounts don't use the IMAP ops queue
|
||||
if account.Provider == models.ProviderOutlookPersonal {
|
||||
return
|
||||
}
|
||||
ops, err := s.db.DequeuePendingOps(account.ID, 50)
|
||||
if err != nil || len(ops) == 0 {
|
||||
return
|
||||
@@ -608,10 +613,10 @@ func (s *Scheduler) ensureFreshToken(account *models.EmailAccount) *models.Email
|
||||
return account
|
||||
}
|
||||
if isOpaque {
|
||||
log.Printf("[oauth:%s] opaque v1 token detected — forcing refresh to get JWT", account.EmailAddress)
|
||||
logger.Debug("[oauth:%s] opaque v1 token detected — forcing refresh to get JWT", account.EmailAddress)
|
||||
}
|
||||
if account.RefreshToken == "" {
|
||||
log.Printf("[oauth:%s] token expired but no refresh token stored — re-authorisation required", account.EmailAddress)
|
||||
logger.Debug("[oauth:%s] token expired but no refresh token stored — re-authorisation required", account.EmailAddress)
|
||||
return account
|
||||
}
|
||||
|
||||
@@ -627,12 +632,12 @@ func (s *Scheduler) ensureFreshToken(account *models.EmailAccount) *models.Email
|
||||
s.cfg.MicrosoftClientID, s.cfg.MicrosoftClientSecret, s.cfg.MicrosoftTenantID,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("[oauth:%s] token refresh failed: %v", account.EmailAddress, err)
|
||||
logger.Debug("[oauth:%s] token refresh failed: %v", account.EmailAddress, err)
|
||||
s.db.SetAccountError(account.ID, "OAuth token refresh failed: "+err.Error())
|
||||
return account // return original; connect will fail and log the error
|
||||
}
|
||||
if err := s.db.UpdateAccountTokens(account.ID, accessTok, refreshTok, expiry); err != nil {
|
||||
log.Printf("[oauth:%s] failed to persist refreshed token: %v", account.EmailAddress, err)
|
||||
logger.Debug("[oauth:%s] failed to persist refreshed token: %v", account.EmailAddress, err)
|
||||
return account
|
||||
}
|
||||
|
||||
@@ -641,7 +646,7 @@ func (s *Scheduler) ensureFreshToken(account *models.EmailAccount) *models.Email
|
||||
if fetchErr != nil || refreshed == nil {
|
||||
return account
|
||||
}
|
||||
log.Printf("[oauth:%s] access token refreshed (expires %s)", account.EmailAddress, expiry.Format("2006-01-02 15:04 UTC"))
|
||||
logger.Debug("[oauth:%s] access token refreshed (expires %s)", account.EmailAddress, expiry.Format("2006-01-02 15:04 UTC"))
|
||||
return refreshed
|
||||
}
|
||||
|
||||
@@ -669,6 +674,42 @@ func (s *Scheduler) SyncFolderNow(accountID, folderID int64) (int, error) {
|
||||
return 0, fmt.Errorf("folder %d not found", folderID)
|
||||
}
|
||||
|
||||
// Graph accounts use the Graph sync path, not IMAP
|
||||
if account.Provider == models.ProviderOutlookPersonal {
|
||||
account = s.ensureFreshToken(account)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
gc := graph.New(account)
|
||||
// Force full resync of this folder by ignoring the since filter
|
||||
msgs, err := gc.ListMessages(ctx, folder.FullPath, time.Time{}, 100)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("graph list messages: %w", err)
|
||||
}
|
||||
n := 0
|
||||
for _, gm := range msgs {
|
||||
msg := &models.Message{
|
||||
AccountID: account.ID,
|
||||
FolderID: folder.ID,
|
||||
RemoteUID: gm.ID,
|
||||
MessageID: gm.InternetMessageID,
|
||||
Subject: gm.Subject,
|
||||
FromName: gm.FromName(),
|
||||
FromEmail: gm.FromEmail(),
|
||||
ToList: gm.ToList(),
|
||||
Date: gm.ReceivedDateTime,
|
||||
IsRead: gm.IsRead,
|
||||
IsStarred: gm.IsFlagged(),
|
||||
HasAttachment: gm.HasAttachments,
|
||||
}
|
||||
if dbErr := s.db.UpsertMessage(msg); dbErr == nil {
|
||||
n++
|
||||
}
|
||||
}
|
||||
// Update folder counts
|
||||
s.db.UpdateFolderCountsDirect(folder.ID, len(msgs), 0)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
account = s.ensureFreshToken(account)
|
||||
@@ -686,7 +727,7 @@ func (s *Scheduler) SyncFolderNow(accountID, folderID int64) (int, error) {
|
||||
// graphWorker is the accountWorker equivalent for ProviderOutlookPersonal accounts.
|
||||
// It polls Graph API instead of using IMAP.
|
||||
func (s *Scheduler) graphWorker(account *models.EmailAccount, stop chan struct{}, push chan struct{}) {
|
||||
log.Printf("[graph] worker started for %s", account.EmailAddress)
|
||||
logger.Debug("[graph] worker started for %s", account.EmailAddress)
|
||||
|
||||
getAccount := func() *models.EmailAccount {
|
||||
a, _ := s.db.GetAccount(account.ID)
|
||||
@@ -705,7 +746,7 @@ func (s *Scheduler) graphWorker(account *models.EmailAccount, stop chan struct{}
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
log.Printf("[graph] worker stopped for %s", account.EmailAddress)
|
||||
logger.Debug("[graph] worker stopped for %s", account.EmailAddress)
|
||||
return
|
||||
case <-push:
|
||||
acc := getAccount()
|
||||
@@ -766,13 +807,11 @@ func (s *Scheduler) graphDeltaSync(account *models.EmailAccount) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine how far back to fetch
|
||||
var since time.Time
|
||||
if account.SyncMode == "days" && account.SyncDays > 0 {
|
||||
since = time.Now().AddDate(0, 0, -account.SyncDays)
|
||||
}
|
||||
|
||||
msgs, err := gc.ListMessages(ctx, gf.ID, since, 500)
|
||||
// Fetch latest messages — no since filter, rely on upsert idempotency.
|
||||
// Graph uses sentDateTime for sent items which differs from receivedDateTime,
|
||||
// making date-based filters unreliable across folder types.
|
||||
// Fetching top 100 newest per folder per sync is efficient enough.
|
||||
msgs, err := gc.ListMessages(ctx, gf.ID, time.Time{}, 100)
|
||||
if err != nil {
|
||||
log.Printf("[graph:%s] list messages in %s: %v", account.EmailAddress, gf.DisplayName, err)
|
||||
continue
|
||||
@@ -805,6 +844,6 @@ func (s *Scheduler) graphDeltaSync(account *models.EmailAccount) {
|
||||
|
||||
s.db.UpdateAccountLastSync(account.ID)
|
||||
if totalNew > 0 {
|
||||
log.Printf("[graph:%s] %d new messages", account.EmailAddress, totalNew)
|
||||
logger.Debug("[graph:%s] %d new messages", account.EmailAddress, totalNew)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user