aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/base/imap.go9
-rw-r--r--plugins/base/routes.go79
-rw-r--r--plugins/base/smtp.go11
-rw-r--r--themes/alps/util.html5
4 files changed, 92 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)
}
}
}
diff --git a/themes/alps/util.html b/themes/alps/util.html
index 68736f1..89e5cac 100644
--- a/themes/alps/util.html
+++ b/themes/alps/util.html
@@ -8,7 +8,11 @@
{{- end -}}
{{- if .HasAttr "\\HasChildren" }}/{{ end }}
+ {{ if eq .Name "Outbox" }}
+ {{ if and (ne .Total -1) (ne .Total 0) }}({{ .Total }} unsent){{ end }}
+ {{ else }}
{{ if and (ne .Unseen -1) (ne .Unseen 0) }}({{ .Unseen }}){{ end }}
+ {{ end }}
</a>
{{ else }}
<span class="noselect">
@@ -26,6 +30,7 @@
{{ with .CategorizedMailboxes }}
{{ with .Common.Inbox }}{{ template "mbox-link" . }}{{ end}}
{{ with .Common.Drafts }}{{ template "mbox-link" . }}{{ end}}
+ {{ with .Common.Outbox }}{{ template "mbox-link" . }}{{ end}}
{{ with .Common.Sent }}{{ template "mbox-link" . }}{{ end}}
{{ with .Common.Junk }}{{ template "mbox-link" . }}{{ end}}
{{ with .Common.Trash }}{{ template "mbox-link" . }}{{ end}}