Skip to content

Commit 40407e0

Browse files
fix(mailbox): write messages before IMAP append wait
1 parent 03a2ce3 commit 40407e0

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

internal/mailbox/imap.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,10 @@ func (t *imapTransport) Send(_ context.Context, from string, d Draft) (SendResul
229229
}
230230
// Best-effort copy to Sent so the message shows in the member's mailbox.
231231
t.mu.Lock()
232-
_, _ = t.c.Append(Sent, int64(len(msg)), nil).Wait() // ignore APPEND errors
232+
appendCmd := t.c.Append(Sent, int64(len(msg)), nil)
233+
_, _ = appendCmd.Write(msg)
234+
_ = appendCmd.Close()
235+
_, _ = appendCmd.Wait()
233236
t.mu.Unlock()
234237
return SendResult{MessageID: msgID}, nil
235238
}

internal/mailbox/imap_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package mailbox
2+
3+
import (
4+
"context"
5+
"net"
6+
"testing"
7+
8+
"github.com/emersion/go-imap/v2"
9+
"github.com/emersion/go-imap/v2/imapserver"
10+
"github.com/emersion/go-imap/v2/imapserver/imapmemserver"
11+
)
12+
13+
func newFakeIMAP(t *testing.T) string {
14+
t.Helper()
15+
16+
backend := imapmemserver.New()
17+
user := imapmemserver.NewUser("alice", "secret")
18+
if err := user.Create(Inbox, nil); err != nil {
19+
t.Fatal(err)
20+
}
21+
if err := user.Create(Sent, nil); err != nil {
22+
t.Fatal(err)
23+
}
24+
backend.AddUser(user)
25+
26+
server := imapserver.New(&imapserver.Options{
27+
NewSession: func(*imapserver.Conn) (imapserver.Session, *imapserver.GreetingData, error) {
28+
return backend.NewSession(), nil, nil
29+
},
30+
InsecureAuth: true,
31+
Caps: imap.CapSet{
32+
imap.CapIMAP4rev1: {},
33+
imap.CapIMAP4rev2: {},
34+
},
35+
})
36+
listener, err := net.Listen("tcp", "127.0.0.1:0")
37+
if err != nil {
38+
t.Fatal(err)
39+
}
40+
go func() { _ = server.Serve(listener) }()
41+
t.Cleanup(func() {
42+
_ = server.Close()
43+
_ = listener.Close()
44+
})
45+
return listener.Addr().String()
46+
}
47+
48+
func TestIMAPSendCopiesMessageToSent(t *testing.T) {
49+
smtpServer := newFakeSMTP(t)
50+
transport, err := NewIMAPTransport(IMAPConfig{
51+
IMAPAddr: newFakeIMAP(t),
52+
SMTPAddr: smtpServer.addr,
53+
Username: "alice",
54+
Password: "secret",
55+
Plaintext: true,
56+
})
57+
if err != nil {
58+
t.Fatal(err)
59+
}
60+
t.Cleanup(func() { _ = transport.Close() })
61+
62+
_, err = transport.Send(context.Background(), "alice@example.test", Draft{
63+
To: []Address{{Address: "bob@example.test"}},
64+
Subject: "Saved message",
65+
Text: "message body",
66+
})
67+
if err != nil {
68+
t.Fatal(err)
69+
}
70+
71+
messages, err := transport.ListMessages(context.Background(), ListOptions{Mailbox: Sent})
72+
if err != nil {
73+
t.Fatal(err)
74+
}
75+
if len(messages) != 1 {
76+
t.Fatalf("Sent contains %d messages; want 1", len(messages))
77+
}
78+
if messages[0].Subject != "Saved message" {
79+
t.Fatalf("Sent subject = %q; want %q", messages[0].Subject, "Saved message")
80+
}
81+
message, found, err := transport.ReadMessage(context.Background(), Sent, messages[0].UID)
82+
if err != nil {
83+
t.Fatal(err)
84+
}
85+
if !found {
86+
t.Fatal("saved message not found in Sent")
87+
}
88+
if message.Text != "message body" {
89+
t.Fatalf("Sent body = %q; want %q", message.Text, "message body")
90+
}
91+
}

0 commit comments

Comments
 (0)