diff --git a/README.md b/README.md
index f0f2175..199ab3b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # go-imap
 
 [![godocs.io](https://godocs.io/github.com/emersion/go-imap?status.svg)](https://godocs.io/github.com/emersion/go-imap)
-[![builds.sr.ht status](https://builds.sr.ht/~emersion/go-imap/commits.svg)](https://builds.sr.ht/~emersion/go-imap/commits?)
+[![builds.sr.ht status](https://builds.sr.ht/~emersion/go-imap/commits/master.svg)](https://builds.sr.ht/~emersion/go-imap/commits/master?)
 
 An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It
 can be used to build a client and/or a server.
@@ -163,6 +163,7 @@ to learn how to use them.
 * [Multi](https://github.com/emersion/go-imap-multi)
 * [PGP](https://github.com/emersion/go-imap-pgp)
 * [Proxy](https://github.com/emersion/go-imap-proxy)
+* [Notmuch](https://github.com/stbenjam/go-imap-notmuch) - Experimental gateway for [Notmuch](https://notmuchmail.org/)
 
 ### Related projects
 
diff --git a/client/client.go b/client/client.go
index 8b6fc84..d93c4cb 100644
--- a/client/client.go
+++ b/client/client.go
@@ -83,6 +83,12 @@ type Client struct {
 	// access.
 	locker sync.Mutex
 
+	// This flag is set when the first search query fails with a BADCHARSET
+	// error. Subsequent queries will be performed with the US-ASCII
+	// charset. According to RFC 3501, SEARCH must only support US-ASCII;
+	// other charsets are optional.
+	utf8SearchUnsupported bool
+
 	// A channel to which unilateral updates from the server will be sent. An
 	// update can be one of: *StatusUpdate, *MailboxUpdate, *MessageUpdate,
 	// *ExpungeUpdate. Note that blocking this channel blocks the whole client,
diff --git a/client/cmd_selected.go b/client/cmd_selected.go
index 0fb71ad..40fc513 100644
--- a/client/cmd_selected.go
+++ b/client/cmd_selected.go
@@ -117,10 +117,15 @@ func (c *Client) executeSearch(uid bool, criteria *imap.SearchCriteria, charset
 }
 
 func (c *Client) search(uid bool, criteria *imap.SearchCriteria) (ids []uint32, err error) {
-	ids, status, err := c.executeSearch(uid, criteria, "UTF-8")
+	charset := "UTF-8"
+	if c.utf8SearchUnsupported {
+		charset = "US-ASCII"
+	}
+	ids, status, err := c.executeSearch(uid, criteria, charset)
 	if status != nil && status.Code == imap.CodeBadCharset {
 		// Some servers don't support UTF-8
 		ids, _, err = c.executeSearch(uid, criteria, "US-ASCII")
+		c.utf8SearchUnsupported = true
 	}
 	return
 }
diff --git a/client/cmd_selected_test.go b/client/cmd_selected_test.go
index b51f0e7..6b6b7e1 100644
--- a/client/cmd_selected_test.go
+++ b/client/cmd_selected_test.go
@@ -144,6 +144,86 @@ func TestClient_Search(t *testing.T) {
 	}
 }
 
+func TestClient_Search_Badcharset(t *testing.T) {
+	c, s := newTestClient(t)
+	defer s.Close()
+
+	setClientState(c, imap.SelectedState, nil)
+
+	date, _ := time.Parse(imap.DateLayout, "1-Feb-1994")
+	criteria := &imap.SearchCriteria{
+		WithFlags: []string{imap.DeletedFlag},
+		Header:    textproto.MIMEHeader{"From": {"Smith"}},
+		Since:     date,
+		Not: []*imap.SearchCriteria{{
+			Header: textproto.MIMEHeader{"To": {"Pauline"}},
+		}},
+	}
+
+	done := make(chan error, 1)
+	var results []uint32
+	go func() {
+		var err error
+		results, err = c.Search(criteria)
+		done <- err
+	}()
+
+	// first search call with default UTF-8 charset (assume server does not support UTF-8)
+	wantCmd := `SEARCH CHARSET UTF-8 SINCE "1-Feb-1994" FROM "Smith" DELETED NOT (TO "Pauline")`
+	tag, cmd := s.ScanCmd()
+	if cmd != wantCmd {
+		t.Fatalf("client sent command %v, want %v", cmd, wantCmd)
+	}
+
+	s.WriteString(tag + " NO [BADCHARSET (US-ASCII)]\r\n")
+
+	// internal fall-back to US-ASCII which sets utf8SearchUnsupported = true
+	wantCmd = `SEARCH CHARSET US-ASCII SINCE "1-Feb-1994" FROM "Smith" DELETED NOT (TO "Pauline")`
+	tag, cmd = s.ScanCmd()
+	if cmd != wantCmd {
+		t.Fatalf("client sent command %v, want %v", cmd, wantCmd)
+	}
+
+	s.WriteString("* SEARCH 2 84 882\r\n")
+	s.WriteString(tag + " OK SEARCH completed\r\n")
+
+	if err := <-done; err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	want := []uint32{2, 84, 882}
+	if !reflect.DeepEqual(results, want) {
+		t.Errorf("c.Search() = %v, want %v", results, want)
+	}
+
+	if !c.utf8SearchUnsupported {
+		t.Fatal("client should have utf8SearchUnsupported set to true")
+	}
+
+	// second call to search (with utf8SearchUnsupported=true)
+	go func() {
+		var err error
+		results, err = c.Search(criteria)
+		done <- err
+	}()
+
+	tag, cmd = s.ScanCmd()
+	if cmd != wantCmd {
+		t.Fatalf("client sent command %v, want %v", cmd, wantCmd)
+	}
+
+	s.WriteString("* SEARCH 2 84 882\r\n")
+	s.WriteString(tag + " OK SEARCH completed\r\n")
+
+	if err := <-done; err != nil {
+		t.Fatalf("c.Search() = %v", err)
+	}
+
+	if !reflect.DeepEqual(results, want) {
+		t.Errorf("c.Search() = %v, want %v", results, want)
+	}
+}
+
 func TestClient_Search_Uid(t *testing.T) {
 	c, s := newTestClient(t)
 	defer s.Close()
@@ -181,6 +261,81 @@ func TestClient_Search_Uid(t *testing.T) {
 	}
 }
 
+func TestClient_Search_Uid_Badcharset(t *testing.T) {
+	c, s := newTestClient(t)
+	defer s.Close()
+
+	setClientState(c, imap.SelectedState, nil)
+
+	criteria := &imap.SearchCriteria{
+		WithoutFlags: []string{imap.DeletedFlag},
+	}
+
+	done := make(chan error, 1)
+	var results []uint32
+	go func() {
+		var err error
+		results, err = c.UidSearch(criteria)
+		done <- err
+	}()
+
+	// first search call with default UTF-8 charset (assume server does not support UTF-8)
+	wantCmd := "UID SEARCH CHARSET UTF-8 UNDELETED"
+	tag, cmd := s.ScanCmd()
+	if cmd != wantCmd {
+		t.Fatalf("client sent command %v, want %v", cmd, wantCmd)
+	}
+
+	s.WriteString(tag + " NO [BADCHARSET (US-ASCII)]\r\n")
+
+	// internal fall-back to US-ASCII which sets utf8SearchUnsupported = true
+	wantCmd = "UID SEARCH CHARSET US-ASCII UNDELETED"
+	tag, cmd = s.ScanCmd()
+	if cmd != wantCmd {
+		t.Fatalf("client sent command %v, want %v", cmd, wantCmd)
+	}
+
+	s.WriteString("* SEARCH 1 78 2010\r\n")
+	s.WriteString(tag + " OK UID SEARCH completed\r\n")
+
+	if err := <-done; err != nil {
+		t.Fatalf("c.UidSearch() = %v", err)
+	}
+
+	want := []uint32{1, 78, 2010}
+	if !reflect.DeepEqual(results, want) {
+		t.Errorf("c.UidSearch() = %v, want %v", results, want)
+	}
+
+	if !c.utf8SearchUnsupported {
+		t.Fatal("client should have utf8SearchUnsupported set to true")
+	}
+
+	// second call to search (with utf8SearchUnsupported=true)
+	go func() {
+		var err error
+		results, err = c.UidSearch(criteria)
+		done <- err
+	}()
+
+	tag, cmd = s.ScanCmd()
+	if cmd != wantCmd {
+		t.Fatalf("client sent command %v, want %v", cmd, wantCmd)
+	}
+
+	s.WriteString("* SEARCH 1 78 2010\r\n")
+	s.WriteString(tag + " OK UID SEARCH completed\r\n")
+
+	if err := <-done; err != nil {
+		t.Fatalf("c.UidSearch() = %v", err)
+	}
+
+	if !reflect.DeepEqual(results, want) {
+		t.Errorf("c.UidSearch() = %v, want %v", results, want)
+	}
+
+}
+
 func TestClient_Fetch(t *testing.T) {
 	c, s := newTestClient(t)
 	defer s.Close()
diff --git a/commands/fetch.go b/commands/fetch.go
index cf45f75..4eb3ab9 100644
--- a/commands/fetch.go
+++ b/commands/fetch.go
@@ -14,14 +14,22 @@ type Fetch struct {
 }
 
 func (cmd *Fetch) Command() *imap.Command {
-	items := make([]interface{}, len(cmd.Items))
-	for i, item := range cmd.Items {
-		items[i] = imap.RawString(item)
-	}
+	// Handle FETCH macros separately as they should not be serialized within parentheses
+	if len(cmd.Items) == 1 && (cmd.Items[0] == imap.FetchAll || cmd.Items[0] == imap.FetchFast || cmd.Items[0] == imap.FetchFull) {
+		return &imap.Command{
+			Name:      "FETCH",
+			Arguments: []interface{}{cmd.SeqSet, imap.RawString(cmd.Items[0])},
+		}
+	} else {
+		items := make([]interface{}, len(cmd.Items))
+		for i, item := range cmd.Items {
+			items[i] = imap.RawString(item)
+		}
 
-	return &imap.Command{
-		Name:      "FETCH",
-		Arguments: []interface{}{cmd.SeqSet, items},
+		return &imap.Command{
+			Name:      "FETCH",
+			Arguments: []interface{}{cmd.SeqSet, items},
+		}
 	}
 }
 
diff --git a/debian/changelog b/debian/changelog
index 60a22da..f90ca95 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-emersion-go-imap (1.2.1+git20220523.1.b8ad7df+ds-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 31 Aug 2022 09:52:54 -0000
+
 golang-github-emersion-go-imap (1.2.0-1) unstable; urgency=medium
 
   * Team Upload.
diff --git a/imap.go b/imap.go
index 837d78d..704c1f3 100644
--- a/imap.go
+++ b/imap.go
@@ -24,7 +24,10 @@ const (
 // A FetchItem is a message data item that can be fetched.
 type FetchItem string
 
-// List of items that can be fetched.
+// List of items that can be fetched. See RFC 3501 section 6.4.5.
+//
+// Warning: FetchBody will not return the raw message body, instead it will
+// return a subset of FetchBodyStructure.
 const (
 	// Macros
 	FetchAll  FetchItem = "ALL"
diff --git a/mailbox.go b/mailbox.go
index e575569..8f12d4d 100644
--- a/mailbox.go
+++ b/mailbox.go
@@ -16,7 +16,7 @@ const InboxName = "INBOX"
 // case-sensitive or case-insensitive depending on the backend implementation.
 // The special INBOX mailbox is case-insensitive.
 func CanonicalMailboxName(name string) string {
-	if strings.ToUpper(name) == InboxName {
+	if strings.EqualFold(name, InboxName) {
 		return InboxName
 	}
 	return name
diff --git a/message.go b/message.go
index 3751b0c..bd28325 100644
--- a/message.go
+++ b/message.go
@@ -61,13 +61,12 @@ const (
 // If the flag is defined in RFC 3501, it returns the flag with the case of the
 // RFC. Otherwise, it returns the lowercase version of the flag.
 func CanonicalFlag(flag string) string {
-	flag = strings.ToLower(flag)
 	for _, f := range flags {
-		if strings.ToLower(f) == flag {
+		if strings.EqualFold(f, flag) {
 			return f
 		}
 	}
-	return flag
+	return strings.ToLower(flag)
 }
 
 func ParseParamList(fields []interface{}) (map[string]string, error) {
diff --git a/server/conn.go b/server/conn.go
index 8929852..37a0356 100644
--- a/server/conn.go
+++ b/server/conn.go
@@ -163,7 +163,7 @@ func (c *conn) Close() error {
 }
 
 func (c *conn) Capabilities() []string {
-	caps := []string{"IMAP4rev1", "LITERAL+", "SASL-IR", "CHILDREN", "UNSELECT", "MOVE"}
+	caps := []string{"IMAP4rev1", "LITERAL+", "SASL-IR", "CHILDREN", "UNSELECT", "MOVE", "IDLE"}
 
 	appendLimitSet := false
 	if c.ctx.State == imap.AuthenticatedState {
diff --git a/server/server_test.go b/server/server_test.go
index e0054bc..32bfe85 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -10,7 +10,7 @@ import (
 )
 
 // Extnesions that are always advertised by go-imap server.
-const builtinExtensions = "LITERAL+ SASL-IR CHILDREN UNSELECT MOVE APPENDLIMIT"
+const builtinExtensions = "LITERAL+ SASL-IR CHILDREN UNSELECT MOVE IDLE APPENDLIMIT"
 
 func testServer(t *testing.T) (s *server.Server, conn net.Conn) {
 	bkd := memory.New()