package jmap import ( "fmt" "io" "strings" "git.sr.ht/~rjarry/aerc/log" "git.sr.ht/~rjarry/aerc/worker/types" "git.sr.ht/~rockorager/go-jmap" "git.sr.ht/~rockorager/go-jmap/mail/email" "git.sr.ht/~rockorager/go-jmap/mail/emailsubmission" "git.sr.ht/~rockorager/go-jmap/mail/mailbox" "github.com/emersion/go-message/mail" ) func (w *JMAPWorker) handleStartSend(msg *types.StartSendingMessage) error { reader, writer := io.Pipe() send := &jmapSendWriter{writer: writer, done: make(chan error)} w.w.PostMessage(&types.MessageWriter{ Message: types.RespondTo(msg), Writer: send, }, nil) go func() { defer log.PanicHandler() defer close(send.done) identity, err := w.getSenderIdentity(msg.Header) if err != nil { send.done <- err return } blob, err := w.Upload(reader) if err != nil { send.done <- err return } var req jmap.Request // Import the blob into drafts req.Invoke(&email.Import{ Account: w.accountId, Emails: map[string]*email.EmailImport{ "aerc": { BlobID: blob.ID, MailboxIDs: map[jmap.ID]bool{ w.roles[mailbox.RoleDrafts]: true, }, Keywords: map[string]bool{ "$draft": true, "$seen": true, }, }, }, }) // Create the submission req.Invoke(&emailsubmission.Set{ Account: w.accountId, Create: map[jmap.ID]*emailsubmission.EmailSubmission{ "sub": { IdentityID: identity, EmailID: "#aerc", }, }, OnSuccessUpdateEmail: map[jmap.ID]jmap.Patch{ "#sub": { "keywords/$draft": nil, w.rolePatch(mailbox.RoleSent): true, w.rolePatch(mailbox.RoleDrafts): nil, }, }, }) resp, err := w.Do(&req) if err != nil { send.done <- err return } for _, inv := range resp.Responses { switch r := inv.Args.(type) { case *email.ImportResponse: if err, ok := r.NotCreated["aerc"]; ok { send.done <- wrapSetError(err) return } case *emailsubmission.SetResponse: if err, ok := r.NotCreated["sub"]; ok { send.done <- wrapSetError(err) return } case *jmap.MethodError: send.done <- wrapMethodError(r) return } } }() return nil } type jmapSendWriter struct { writer *io.PipeWriter done chan error } func (w *jmapSendWriter) Write(data []byte) (int, error) { return w.writer.Write(data) } func (w *jmapSendWriter) Close() error { writeErr := w.writer.Close() sendErr := <-w.done if writeErr != nil { return writeErr } return sendErr } func (w *JMAPWorker) getSenderIdentity(header *mail.Header) (jmap.ID, error) { from, err := header.AddressList("from") if err != nil { return "", fmt.Errorf("msg.Header.AddressList: %w", err) } if len(from) != 1 { return "", fmt.Errorf("no from header in message") } name, domain, _ := strings.Cut(from[0].Address, "@") for _, ident := range w.identities { n, d, _ := strings.Cut(ident.Email, "@") switch { case n == name && d == domain: fallthrough case n == "*" && d == domain: return ident.ID, nil } } return "", fmt.Errorf("no identity found for address: %s@%s", name, domain) }