From 611334b3e21558466df382fb1825b702cc9ee8a9 Mon Sep 17 00:00:00 2001 From: zhouliang Date: Thu, 26 Feb 2026 02:26:22 +0800 Subject: [PATCH] feat: support WangYi email 1. Add support for WangYi email (e.g., 163.com, 126.com) IMAP connection 2. For WangYi email, the ID command must be executed before the SELECT/EXAMINE command to establish a secure connection; otherwise, the error "SELECT Unsafe Login. Please contact kefu@1888.com for help" will be thrown 3. When using the IDLE command for WangYi email, multiple messages will be returned by the server upon receiving a new email --- pkg/channels/email/email.go | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/pkg/channels/email/email.go b/pkg/channels/email/email.go index d716a3c9d..c403c85df 100644 --- a/pkg/channels/email/email.go +++ b/pkg/channels/email/email.go @@ -299,8 +299,18 @@ func (c *EmailChannel) connect() error { if mailbox == "" { mailbox = "INBOX" } - - status, err := cl.Select(mailbox, false) + if c.checkIsWangYiEmail() { + // wangyi is not follow the IMAPz specification, it requires to use ID COMMAND to select mailbox, you need to add ID COMMAND to the select command + // if not, it will return "SELECT Unsafe Login. Please contact kefu@1888.com for help" error + cmd := &imap.Command{Name: "ID", Arguments: []any{ + []any{"name", "picoclaw", "version", "1.0"}, + }} + _, idErr := cl.Execute(cmd, nil) + if idErr != nil { + return fmt.Errorf("failed to execute ID COMMAND: %w", idErr) + } + } + status, err := cl.Select(mailbox, true) if err != nil { if strings.Contains(err.Error(), "Unsafe Login") || strings.Contains(err.Error(), "不安全") { //nolint:gosmopolitan return fmt.Errorf( @@ -335,6 +345,13 @@ func (c *EmailChannel) connect() error { return nil } +func (c *EmailChannel) checkIsWangYiEmail() bool { + server := strings.ToLower(c.config.IMAPServer) + return strings.Contains(server, "163.com") || + strings.Contains(server, "126.com") || + strings.Contains(server, "yeah.net") +} + // syncLastUID fetches the mailbox max UID and sets lastUID so only mail after connect is processed. func (c *EmailChannel) syncLastUID(cl *client.Client) error { c.mu.Lock() @@ -594,7 +611,7 @@ func (c *EmailChannel) CheckNewEmails(ctx context.Context) { criteria.Uid = seqset } - uids, err := cl.UidSearch(criteria) + searchUids, err := cl.UidSearch(criteria) if err != nil { logger.ErrorCF("email", "Failed to search emails", map[string]any{ "error": err.Error(), @@ -608,6 +625,14 @@ func (c *EmailChannel) CheckNewEmails(ctx context.Context) { continue } + // a new email may correspond to multiple IMAP updates + // use max uid to avoid duplicate + uids := make([]uint32, 0, len(searchUids)) + for _, uid := range searchUids { + if uid > lastUID { + uids = append(uids, uid) + } + } if len(uids) == 0 { return }