diff options
author | Drew DeVault <sir@cmpwn.com> | 2020-10-30 11:47:23 -0400 |
---|---|---|
committer | Drew DeVault <sir@cmpwn.com> | 2020-10-30 11:47:23 -0400 |
commit | cbeacf9d060fbd8c593aa3a9a73d9fe94b9ee5d1 (patch) | |
tree | 125353bfc6cccc6e028fd456c471a0ea07a065f0 /plugins | |
parent | d325628cb2b0ad49790549367d1f792fc0f4735e (diff) | |
download | alps-cbeacf9d060fbd8c593aa3a9a73d9fe94b9ee5d1.tar.gz alps-cbeacf9d060fbd8c593aa3a9a73d9fe94b9ee5d1.zip |
Copy unsent messages to Outbox
This patch:
1. Copies unsent messages to the outbox before attempting to deliver
them with SMTP
2. Deletes those messages once they're sent, or leaves them if an error
occured
3. Updates the message list to make it obvious when there are unsent
messages in the outbox
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/base/imap.go | 9 | ||||
-rw-r--r-- | plugins/base/routes.go | 79 | ||||
-rw-r--r-- | plugins/base/smtp.go | 11 |
3 files changed, 87 insertions, 12 deletions
diff --git a/plugins/base/imap.go b/plugins/base/imap.go index f8065b0..85cd428 100644 --- a/plugins/base/imap.go +++ b/plugins/base/imap.go @@ -22,6 +22,7 @@ type MailboxInfo struct { *imap.MailboxInfo Active bool + Total int Unseen int } @@ -49,7 +50,7 @@ func listMailboxes(conn *imapclient.Client) ([]MailboxInfo, error) { var mailboxes []MailboxInfo for mbox := range ch { - mailboxes = append(mailboxes, MailboxInfo{mbox, false, -1}) + mailboxes = append(mailboxes, MailboxInfo{mbox, false, -1, -1}) } if err := <-done; err != nil { @@ -95,6 +96,7 @@ type mailboxType int const ( mailboxSent mailboxType = iota + mailboxOutbox mailboxType = iota mailboxDrafts ) @@ -115,6 +117,9 @@ func getMailboxByType(conn *imapclient.Client, mboxType mailboxType) (*MailboxIn case mailboxDrafts: attr = imapspecialuse.Drafts fallbackNames = []string{"Draft", "Drafts"} + case mailboxOutbox: + attr = "" + fallbackNames = []string{"Outbox"} } var attrMatched bool @@ -146,7 +151,7 @@ func getMailboxByType(conn *imapclient.Client, mboxType mailboxType) (*MailboxIn if best == nil { return nil, nil } - return &MailboxInfo{best, false, -1}, nil + return &MailboxInfo{best, false, -1, -1}, nil } func ensureMailboxSelected(conn *imapclient.Client, mboxName string) error { diff --git a/plugins/base/routes.go b/plugins/base/routes.go index ce72255..7e30513 100644 --- a/plugins/base/routes.go +++ b/plugins/base/routes.go @@ -73,6 +73,7 @@ type IMAPBaseRenderData struct { Mailboxes []MailboxInfo Mailbox *MailboxStatus Inbox *MailboxStatus + Outbox *MailboxStatus } type MailboxRenderData struct { @@ -87,6 +88,7 @@ type CategorizedMailboxes struct { Common struct { Inbox *MailboxInfo Drafts *MailboxInfo + Outbox *MailboxInfo Sent *MailboxInfo Junk *MailboxInfo Trash *MailboxInfo @@ -104,7 +106,7 @@ func newIMAPBaseRenderData(ctx *alps.Context, } var mailboxes []MailboxInfo - var active, inbox *MailboxStatus + var active, inbox, outbox *MailboxStatus err = ctx.Session.DoIMAP(func(c *imapclient.Client) error { var err error if mailboxes, err = listMailboxes(c); err != nil { @@ -122,6 +124,13 @@ func newIMAPBaseRenderData(ctx *alps.Context, return err } } + if mboxName == "Outbox" { + outbox = active + } else { + if outbox, err = getMailboxStatus(c, "Outbox"); err != nil { + return err + } + } return nil }) if err != nil { @@ -132,6 +141,7 @@ func newIMAPBaseRenderData(ctx *alps.Context, mmap := map[string]**MailboxInfo{ "INBOX": &categorized.Common.Inbox, "Drafts": &categorized.Common.Drafts, + "Outbox": &categorized.Common.Outbox, "Sent": &categorized.Common.Sent, "Junk": &categorized.Common.Junk, "Trash": &categorized.Common.Trash, @@ -142,10 +152,16 @@ func newIMAPBaseRenderData(ctx *alps.Context, // Populate unseen & active states if active != nil && mailboxes[i].Name == active.Name { mailboxes[i].Unseen = int(active.Unseen) + mailboxes[i].Total = int(active.Messages) mailboxes[i].Active = true } if mailboxes[i].Name == inbox.Name { mailboxes[i].Unseen = int(inbox.Unseen) + mailboxes[i].Total = int(inbox.Messages) + } + if mailboxes[i].Name == outbox.Name { + mailboxes[i].Unseen = int(outbox.Unseen) + mailboxes[i].Total = int(outbox.Messages) } if ptr, ok := mmap[mailboxes[i].Name]; ok { @@ -416,8 +432,23 @@ type composeOptions struct { // Send message, append it to the Sent mailbox, mark the original message as // answered func submitCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOptions) error { - msg.Ref() - msg.Ref() + msg.Ref(3) + + err := ctx.Session.DoIMAP(func(c *imapclient.Client) error { + // (disregard error, we don't care if Outbox already existed) + c.Create("Outbox") + + if _, err := appendMessage(c, msg, mailboxOutbox); err != nil { + return err + } + + msg.Unref() + return nil + }) + if err != nil { + return fmt.Errorf("failed to save message to outbox: %v", err) + } + task := work.NewTask(func(_ context.Context) error { err := ctx.Session.DoSMTP(func (c *smtp.Client) error { return sendMessage(c, msg) @@ -427,9 +458,43 @@ func submitCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOpti } return err }).Retries(5).After(func(_ context.Context, task *work.Task) { + ctx.Logger().Printf("email sent: %v", task.Result()) + if task.Result() == nil { + // Remove from outbox + err := ctx.Session.DoIMAP(func(c *imapclient.Client) error { + ctx.Logger().Printf("DoIMAP") + if err := ensureMailboxSelected(c, "Outbox"); err != nil { + return err + } + uids, err := c.UidSearch(&imap.SearchCriteria{ + Header: map[string][]string{ + "Message-Id": []string{msg.MessageID}, + }, + }) + if err != nil { + return fmt.Errorf("UID SEARCH failed: %v", err) + } + if len(uids) == 1 { + if err = deleteMessage(c, "Outbox", uids[0]); err != nil { + return err + } + } else { + ctx.Logger().Errorf( + "Unexpectedly found multiple results in outbox for message ID %s", + msg.MessageID) + } + return nil + }) + if err != nil { + ctx.Logger().Errorf("Error removing message from outbox: %v", err) + } + } else { + ctx.Logger().Errorf("Message delivery failed with error %v", err) + } + msg.Unref() }) - err := ctx.Server.Queue.Enqueue(task) + err = ctx.Server.Queue.Enqueue(task) if err != nil { if _, ok := err.(alps.AuthError); ok { return echo.NewHTTPError(http.StatusForbidden, err) @@ -451,6 +516,7 @@ func submitCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOpti return err } msg.Unref() + if draft := options.Draft; draft != nil { if err := deleteMessage(c, draft.Mailbox, draft.Uid); err != nil { return err @@ -599,6 +665,7 @@ func handleComposeNew(ctx *alps.Context) error { To: strings.Split(ctx.QueryParam("to"), ","), Subject: ctx.QueryParam("subject"), Text: ctx.QueryParam("body"), + MessageID: mail.GenerateMessageID(), InReplyTo: ctx.QueryParam("in-reply-to"), }, &composeOptions{}) } @@ -676,6 +743,7 @@ func handleReply(ctx *alps.Context) error { return err } + msg.MessageID = mail.GenerateMessageID() msg.InReplyTo = inReplyTo.Envelope.MessageId // TODO: populate From from known user addresses and inReplyTo.Envelope.To replyTo := inReplyTo.Envelope.ReplyTo @@ -734,6 +802,7 @@ func handleForward(ctx *alps.Context) error { return err } + msg.MessageID = mail.GenerateMessageID() msg.Subject = source.Envelope.Subject if !strings.HasPrefix(strings.ToLower(msg.Subject), "fwd:") && !strings.HasPrefix(strings.ToLower(msg.Subject), "fw:") { @@ -807,7 +876,7 @@ func handleEdit(ctx *alps.Context) error { msg.To = unwrapIMAPAddressList(source.Envelope.To) msg.Subject = source.Envelope.Subject msg.InReplyTo = source.Envelope.InReplyTo - // TODO: preserve Message-Id + msg.MessageID = source.Envelope.MessageId attachments := source.Attachments() for i := range attachments { diff --git a/plugins/base/smtp.go b/plugins/base/smtp.go index a6cfd3d..50a2fd0 100644 --- a/plugins/base/smtp.go +++ b/plugins/base/smtp.go @@ -73,8 +73,8 @@ func (att *refcountedAttachment) Filename() string { return att.FileHeader.Filename } -func (att *refcountedAttachment) Ref() { - att.refs += 1 +func (att *refcountedAttachment) Ref(n int) { + att.refs += n } func (att *refcountedAttachment) Unref() { @@ -111,6 +111,7 @@ type OutgoingMessage struct { From string To []string Subject string + MessageID string InReplyTo string Text string Attachments []Attachment @@ -170,7 +171,7 @@ func (msg *OutgoingMessage) WriteTo(w io.Writer) error { h.Set("In-Reply-To", msg.InReplyTo) } - h.Set("Message-Id", mail.GenerateMessageID()) + h.Set("Message-Id", msg.MessageID) mw, err := mail.CreateWriter(w, h) if err != nil { @@ -207,10 +208,10 @@ func (msg *OutgoingMessage) WriteTo(w io.Writer) error { return nil } -func (msg *OutgoingMessage) Ref() { +func (msg *OutgoingMessage) Ref(n int) { for _, a := range msg.Attachments { if a, ok := a.(*refcountedAttachment); ok { - a.Ref() + a.Ref(n) } } } |