Repository: emersion/go-imap
Pull request #47 (2016-08-11 12:22:32)

v1 API
Breaking changes for packages common and server.


Patch:
diff --git a/README.md b/README.md
index aca273f..73a461b 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ import (
 	"log"
 
 	"github.com/emersion/go-imap/client"
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 func main() {
diff --git a/backend/mailbox.go b/backend/mailbox.go
index 01cf90e..d33dc3e 100644
--- a/backend/mailbox.go
+++ b/backend/mailbox.go
@@ -3,7 +3,7 @@ package backend
 import (
 	"time"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // Mailbox represents a mailbox belonging to a user in the mail storage system.
@@ -13,11 +13,11 @@ type Mailbox interface {
 	Name() string
 
 	// Get this mailbox info.
-	Info() (*common.MailboxInfo, error)
+	Info() (*imap.MailboxInfo, error)
 
 	// Get this mailbox status.
 	// See RFC 3501 section 6.3.10 for a list of items that can be requested.
-	Status(items []string) (*common.MailboxStatus, error)
+	Status(items []string) (*imap.MailboxStatus, error)
 
 	// Add the mailbox to the server's set of "active" or "subscribed" mailboxes.
 	Subscribe() error
@@ -40,10 +40,10 @@ type Mailbox interface {
 	// See RFC 3501 section 6.4.5 for a list of items that can be requested.
 	//
 	// Messages must be sent to ch. When the function returns, ch must be closed.
-	ListMessages(uid bool, seqset *common.SeqSet, items []string, ch chan<- *common.Message) error
+	ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch chan<- *imap.Message) error
 
 	// Search messages.
-	SearchMessages(uid bool, criteria *common.SearchCriteria) ([]uint32, error)
+	SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error)
 
 	// Append a new message to this mailbox. The \Recent flag will be added no
 	// matter flags is empty or not. If date is nil, the current time will be
@@ -51,13 +51,13 @@ type Mailbox interface {
 	//
 	// If the Backend implements Updater, it must notify the client immediately
 	// via a mailbox update.
-	CreateMessage(flags []string, date *time.Time, body []byte) error
+	CreateMessage(flags []string, date time.Time, body []byte) error
 
 	// Alter flags for the specified message(s).
 	//
 	// If the Backend implements Updater, it must notify the client immediately
 	// via a message update.
-	UpdateMessagesFlags(uid bool, seqset *common.SeqSet, operation common.FlagsOp, flags []string) error
+	UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, flags []string) error
 
 	// Copy the specified message(s) to the end of the specified destination
 	// mailbox. The flags and internal date of the message(s) SHOULD be preserved,
@@ -68,7 +68,7 @@ type Mailbox interface {
 	//
 	// If the Backend implements Updater, it must notify the client immediately
 	// via a mailbox update.
-	CopyMessages(uid bool, seqset *common.SeqSet, dest string) error
+	CopyMessages(uid bool, seqset *imap.SeqSet, dest string) error
 
 	// Permanently removes all messages that have the \Deleted flag set from the
 	// currently selected mailbox.
diff --git a/backend/memory/backend.go b/backend/memory/backend.go
index 28483f4..d3c6b32 100644
--- a/backend/memory/backend.go
+++ b/backend/memory/backend.go
@@ -5,8 +5,8 @@ import (
 	"errors"
 	"time"
 
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/backend"
-	"github.com/emersion/go-imap/common"
 )
 
 type Backend struct {
@@ -39,19 +39,19 @@ Hi there :)`
 		"INBOX": &Mailbox{
 			name: "INBOX",
 			messages: []*Message{
-				&Message{&common.Message{
-					Uid: 6,
+				&Message{&imap.Message{
+					Uid:   6,
 					Flags: []string{"\\Seen"},
-					Envelope: &common.Envelope{
-						Date: &now,
+					Envelope: &imap.Envelope{
+						Date:    now,
 						Subject: "Hello World!",
-						From: []*common.Address{},
-						Sender: []*common.Address{},
-						To: []*common.Address{},
+						From:    []*imap.Address{},
+						Sender:  []*imap.Address{},
+						To:      []*imap.Address{},
 					},
-					BodyStructure: &common.BodyStructure{MimeType: "text", MimeSubType: "plain"},
-					Size: uint32(len(body)),
-					InternalDate: &now,
+					BodyStructure: &imap.BodyStructure{MimeType: "text", MimeSubType: "plain"},
+					Size:          uint32(len(body)),
+					InternalDate:  now,
 				}, []byte(body)},
 			},
 			user: user,
diff --git a/backend/memory/mailbox.go b/backend/memory/mailbox.go
index 944b79e..a594a44 100644
--- a/backend/memory/mailbox.go
+++ b/backend/memory/mailbox.go
@@ -4,24 +4,24 @@ import (
 	"errors"
 	"time"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 type Mailbox struct {
-	name string
+	name       string
 	subscribed bool
-	messages []*Message
-	user *User
+	messages   []*Message
+	user       *User
 }
 
 func (mbox *Mailbox) Name() string {
 	return mbox.name
 }
 
-func (mbox *Mailbox) Info() (*common.MailboxInfo, error) {
-	info := &common.MailboxInfo{
-		Delimiter: "/",
-		Name: mbox.name,
+func (mbox *Mailbox) Info() (*imap.MailboxInfo, error) {
+	info := &imap.MailboxInfo{
+		Delimiter:  "/",
+		Name:       mbox.name,
 		Attributes: []string{"\\Noinferiors"},
 	}
 	return info, nil
@@ -37,11 +37,11 @@ func (mbox *Mailbox) uidNext() (uid uint32) {
 	return
 }
 
-func (mbox *Mailbox) Status(items []string) (*common.MailboxStatus, error) {
-	status := &common.MailboxStatus{
-		Items: items,
-		Name: mbox.name,
-		Flags: []string{"\\Answered", "\\Flagged", "\\Deleted", "\\Seen", "\\Draft"},
+func (mbox *Mailbox) Status(items []string) (*imap.MailboxStatus, error) {
+	status := &imap.MailboxStatus{
+		Items:          items,
+		Name:           mbox.name,
+		Flags:          []string{"\\Answered", "\\Flagged", "\\Deleted", "\\Seen", "\\Draft"},
 		PermanentFlags: []string{"\\Answered", "\\Flagged", "\\Deleted", "\\Seen", "\\Draft", "\\*"},
 	}
 
@@ -77,11 +77,11 @@ func (mbox *Mailbox) Check() error {
 	return nil
 }
 
-func (mbox *Mailbox) ListMessages(uid bool, seqset *common.SeqSet, items []string, ch chan<- *common.Message) (err error) {
+func (mbox *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch chan<- *imap.Message) (err error) {
 	defer close(ch)
 
 	for i, msg := range mbox.messages {
-		seqNum := uint32(i+1)
+		seqNum := uint32(i + 1)
 
 		var id uint32
 		if uid {
@@ -101,7 +101,7 @@ func (mbox *Mailbox) ListMessages(uid bool, seqset *common.SeqSet, items []strin
 	return
 }
 
-func (mbox *Mailbox) SearchMessages(uid bool, criteria *common.SearchCriteria) (ids []uint32, err error) {
+func (mbox *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) (ids []uint32, err error) {
 	for i, msg := range mbox.messages {
 		if !msg.Matches(criteria) {
 			continue
@@ -111,7 +111,7 @@ func (mbox *Mailbox) SearchMessages(uid bool, criteria *common.SearchCriteria) (
 		if uid {
 			id = msg.Uid
 		} else {
-			id = uint32(i+1)
+			id = uint32(i + 1)
 		}
 		ids = append(ids, id)
 	}
@@ -119,44 +119,43 @@ func (mbox *Mailbox) SearchMessages(uid bool, criteria *common.SearchCriteria) (
 	return
 }
 
-func (mbox *Mailbox) CreateMessage(flags []string, date *time.Time, body []byte) error {
-	if date == nil {
-		now := time.Now()
-		date = &now
+func (mbox *Mailbox) CreateMessage(flags []string, date time.Time, body []byte) error {
+	if date.IsZero() {
+		date = time.Now()
 	}
 
-	mbox.messages = append(mbox.messages, &Message{&common.Message{
-		Uid: mbox.uidNext(),
-		Envelope: &common.Envelope{},
-		BodyStructure: &common.BodyStructure{MimeType: "text", MimeSubType: "plain"},
-		Size: uint32(len(body)),
-		InternalDate: date,
-		Flags: flags,
+	mbox.messages = append(mbox.messages, &Message{&imap.Message{
+		Uid:           mbox.uidNext(),
+		Envelope:      &imap.Envelope{},
+		BodyStructure: &imap.BodyStructure{MimeType: "text", MimeSubType: "plain"},
+		Size:          uint32(len(body)),
+		InternalDate:  date,
+		Flags:         flags,
 	}, body})
 
 	return nil
 }
 
-func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqset *common.SeqSet, op common.FlagsOp, flags []string) error {
+func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, op imap.FlagsOp, flags []string) error {
 	for i, msg := range mbox.messages {
 		var id uint32
 		if uid {
 			id = msg.Uid
 		} else {
-			id = uint32(i+1)
+			id = uint32(i + 1)
 		}
 		if !seqset.Contains(id) {
 			continue
 		}
 
 		switch op {
-		case common.SetFlags:
+		case imap.SetFlags:
 			// TODO: keep \Recent if it is present
 			msg.Flags = flags
-		case common.AddFlags:
+		case imap.AddFlags:
 			// TODO: check for duplicates
 			msg.Flags = append(msg.Flags, flags...)
-		case common.RemoveFlags:
+		case imap.RemoveFlags:
 			// Iterate through flags from the last one to the first one, to be able to
 			// delete some of them.
 			for i := len(msg.Flags) - 1; i >= 0; i-- {
@@ -175,7 +174,7 @@ func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqset *common.SeqSet, op com
 	return nil
 }
 
-func (mbox *Mailbox) CopyMessages(uid bool, seqset *common.SeqSet, destName string) error {
+func (mbox *Mailbox) CopyMessages(uid bool, seqset *imap.SeqSet, destName string) error {
 	dest, ok := mbox.user.mailboxes[destName]
 	if !ok {
 		return errors.New("Destination mailbox doesn't exist")
@@ -186,7 +185,7 @@ func (mbox *Mailbox) CopyMessages(uid bool, seqset *common.SeqSet, destName stri
 		if uid {
 			id = msg.Uid
 		} else {
-			id = uint32(i+1)
+			id = uint32(i + 1)
 		}
 		if !seqset.Contains(id) {
 			continue
diff --git a/backend/memory/message.go b/backend/memory/message.go
index c033486..13e88a3 100644
--- a/backend/memory/message.go
+++ b/backend/memory/message.go
@@ -3,18 +3,18 @@ package memory
 import (
 	"bytes"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 type Message struct {
-	*common.Message
+	*imap.Message
 
 	body []byte
 }
 
-func (m *Message) Metadata(items []string) (metadata *common.Message) {
-	metadata = &common.Message{
-		Body: map[*common.BodySectionName]*common.Literal{},
+func (m *Message) Metadata(items []string) (metadata *imap.Message) {
+	metadata = &imap.Message{
+		Body: map[*imap.BodySectionName]*imap.Literal{},
 	}
 
 	for _, item := range items {
@@ -32,7 +32,7 @@ func (m *Message) Metadata(items []string) (metadata *common.Message) {
 		case "UID":
 			metadata.Uid = m.Uid
 		default:
-			section, err := common.NewBodySectionName(item)
+			section, err := imap.NewBodySectionName(item)
 			item = ""
 			if err != nil {
 				break
@@ -60,9 +60,9 @@ func (m *Message) Metadata(items []string) (metadata *common.Message) {
 			}
 
 			// If part doesn't exist, set the literal to nil
-			var literal *common.Literal
+			var literal *imap.Literal
 			if body != nil {
-				literal = common.NewLiteral(section.ExtractPartial(body))
+				literal = imap.NewLiteral(section.ExtractPartial(body))
 			}
 			metadata.Body[section] = literal
 		}
@@ -84,12 +84,12 @@ func (m *Message) hasFlag(flag string) bool {
 	return false
 }
 
-func (m *Message) Matches(criteria *common.SearchCriteria) bool {
+func (m *Message) Matches(criteria *imap.SearchCriteria) bool {
 	// TODO
 	if criteria.SeqSet != nil && !criteria.SeqSet.Contains(m.SeqNum) {
 		return false
 	}
-	if criteria.Deleted && !m.hasFlag(common.DeletedFlag) {
+	if criteria.Deleted && !m.hasFlag(imap.DeletedFlag) {
 		return false
 	}
 	return true
diff --git a/backend/memory/user.go b/backend/memory/user.go
index 501698f..4adac37 100644
--- a/backend/memory/user.go
+++ b/backend/memory/user.go
@@ -7,8 +7,8 @@ import (
 )
 
 type User struct {
-	username string
-	password string
+	username  string
+	password  string
 	mailboxes map[string]*Mailbox
 }
 
@@ -63,9 +63,9 @@ func (u *User) RenameMailbox(existingName, newName string) error {
 	}
 
 	u.mailboxes[newName] = &Mailbox{
-		name: newName,
+		name:     newName,
 		messages: mbox.messages,
-		user: u,
+		user:     u,
 	}
 
 	mbox.messages = nil
diff --git a/backend/updates.go b/backend/updates.go
index 1458014..60f5830 100644
--- a/backend/updates.go
+++ b/backend/updates.go
@@ -1,7 +1,7 @@
 package backend
 
 import (
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // Update contains user and mailbox information about an unilateral backend
@@ -20,19 +20,19 @@ type Update struct {
 // A status update. See RFC 3501 section 7.1 for a list of status responses.
 type StatusUpdate struct {
 	Update
-	*common.StatusResp
+	*imap.StatusResp
 }
 
 // A mailbox update.
 type MailboxUpdate struct {
 	Update
-	*common.MailboxStatus
+	*imap.MailboxStatus
 }
 
 // A message update.
 type MessageUpdate struct {
 	Update
-	*common.Message
+	*imap.Message
 }
 
 // An expunge update.
@@ -43,10 +43,10 @@ type ExpungeUpdate struct {
 
 // Updates contains channels where unilateral backend updates will be sent.
 type Updates struct {
-	Statuses chan *StatusUpdate
+	Statuses  chan *StatusUpdate
 	Mailboxes chan *MailboxUpdate
-	Messages chan *MessageUpdate
-	Expunges chan *ExpungeUpdate
+	Messages  chan *MessageUpdate
+	Expunges  chan *ExpungeUpdate
 }
 
 // Send sends all specified updates. It panics if one of the provided value is
@@ -71,10 +71,10 @@ func (U *Updates) Send(updates ...interface{}) {
 // NewUpdates initializes a new Updates struct.
 func NewUpdates() (up *Updates) {
 	return &Updates{
-		Statuses: make(chan *StatusUpdate),
+		Statuses:  make(chan *StatusUpdate),
 		Mailboxes: make(chan *MailboxUpdate),
-		Messages: make(chan *MessageUpdate),
-		Expunges: make(chan *ExpungeUpdate),
+		Messages:  make(chan *MessageUpdate),
+		Expunges:  make(chan *ExpungeUpdate),
 	}
 }
 
diff --git a/client/client.go b/client/client.go
index 0b9e227..a4877ca 100644
--- a/client/client.go
+++ b/client/client.go
@@ -8,17 +8,17 @@ import (
 	"net"
 	"sync"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // An IMAP client.
 type Client struct {
-	conn *imap.Conn
+	conn  *imap.Conn
 	isTLS bool
 
-	handlers []imap.RespHandler
+	handlers       []imap.RespHandler
 	handlersLocker sync.Locker
-	loggedOut chan struct{}
+	loggedOut      chan struct{}
 
 	// The server capabilities.
 	Caps map[string]bool
@@ -48,7 +48,7 @@ type Client struct {
 func (c *Client) read() error {
 	r := c.conn.Reader
 
-	defer (func () {
+	defer (func() {
 		c.handlersLocker.Lock()
 		defer c.handlersLocker.Unlock()
 
@@ -89,7 +89,7 @@ func (c *Client) read() error {
 			}
 
 			h := &imap.RespHandling{
-				Resp: res,
+				Resp:    res,
 				Accepts: make(chan bool),
 			}
 
@@ -141,20 +141,19 @@ func (c *Client) execute(cmdr imap.Commander, res imap.RespHandlerFrom) (status
 	c.addHandler(statusHdlr)
 	defer c.removeHandler(statusHdlr)
 
-	_, err = cmd.WriteTo(c.conn.Writer)
-	if err != nil {
-		return
-	}
-
-	if err = c.conn.Flush(); err != nil {
+	if err = cmd.WriteTo(c.conn); err != nil {
 		return
 	}
 
 	var hdlr imap.RespHandler
 	var done chan error
-	defer (func () {
-		if hdlr != nil { close(hdlr) }
-		if done != nil { close(done) }
+	defer (func() {
+		if hdlr != nil {
+			close(hdlr)
+		}
+		if done != nil {
+			close(done)
+		}
 	})()
 
 	if res != nil {
@@ -385,10 +384,10 @@ func New(conn net.Conn) (c *Client, err error) {
 	r := imap.NewReader(nil)
 
 	c = &Client{
-		conn: imap.NewConn(conn, r, w),
+		conn:           imap.NewConn(conn, r, w),
 		handlersLocker: &sync.Mutex{},
-		loggedOut: make(chan struct{}),
-		State: imap.NotAuthenticatedState,
+		loggedOut:      make(chan struct{}),
+		State:          imap.NotAuthenticatedState,
 	}
 
 	go c.handleContinuationReqs(continues)
diff --git a/client/cmd_any.go b/client/cmd_any.go
index e3488f1..51fb8a0 100644
--- a/client/cmd_any.go
+++ b/client/cmd_any.go
@@ -3,7 +3,7 @@ package client
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/commands"
 	"github.com/emersion/go-imap/responses"
 )
diff --git a/client/cmd_auth.go b/client/cmd_auth.go
index fc965f3..d0c11d0 100644
--- a/client/cmd_auth.go
+++ b/client/cmd_auth.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"time"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/commands"
 	"github.com/emersion/go-imap/responses"
 )
@@ -24,7 +24,7 @@ func (c *Client) Select(name string, readOnly bool) (mbox *imap.MailboxStatus, e
 	mbox = &imap.MailboxStatus{Name: name}
 
 	cmd := &commands.Select{
-		Mailbox: name,
+		Mailbox:  name,
 		ReadOnly: readOnly,
 	}
 	res := &responses.Select{
@@ -99,7 +99,7 @@ func (c *Client) Rename(existingName, newName string) (err error) {
 
 	cmd := &commands.Rename{
 		Existing: existingName,
-		New: newName,
+		New:      newName,
 	}
 
 	status, err := c.execute(cmd, nil)
@@ -169,7 +169,7 @@ func (c *Client) List(ref, name string, ch chan *imap.MailboxInfo) (err error) {
 
 	cmd := &commands.List{
 		Reference: ref,
-		Mailbox: name,
+		Mailbox:   name,
 	}
 	res := &responses.List{Mailboxes: ch}
 
@@ -193,12 +193,12 @@ func (c *Client) Lsub(ref, name string, ch chan *imap.MailboxInfo) (err error) {
 	}
 
 	cmd := &commands.List{
-		Reference: ref,
-		Mailbox: name,
+		Reference:  ref,
+		Mailbox:    name,
 		Subscribed: true,
 	}
 	res := &responses.List{
-		Mailboxes: ch,
+		Mailboxes:  ch,
 		Subscribed: true,
 	}
 
@@ -225,7 +225,7 @@ func (c *Client) Status(name string, items []string) (mbox *imap.MailboxStatus,
 
 	cmd := &commands.Status{
 		Mailbox: name,
-		Items: items,
+		Items:   items,
 	}
 	res := &responses.Status{
 		Mailbox: mbox,
@@ -244,7 +244,7 @@ func (c *Client) Status(name string, items []string) (mbox *imap.MailboxStatus,
 // destination mailbox. This argument SHOULD be in the format of an RFC 2822
 // message.
 // flags and date are optional arguments and can be set to nil.
-func (c *Client) Append(mbox string, flags []string, date *time.Time, msg *imap.Literal) (err error) {
+func (c *Client) Append(mbox string, flags []string, date time.Time, msg *imap.Literal) (err error) {
 	if c.State != imap.AuthenticatedState && c.State != imap.SelectedState {
 		err = ErrNotLoggedIn
 		return
@@ -252,8 +252,8 @@ func (c *Client) Append(mbox string, flags []string, date *time.Time, msg *imap.
 
 	cmd := &commands.Append{
 		Mailbox: mbox,
-		Flags: flags,
-		Date: date,
+		Flags:   flags,
+		Date:    date,
 		Message: msg,
 	}
 
diff --git a/client/cmd_noauth.go b/client/cmd_noauth.go
index c2cb467..54405c9 100644
--- a/client/cmd_noauth.go
+++ b/client/cmd_noauth.go
@@ -5,20 +5,20 @@ import (
 	"errors"
 	"net"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/commands"
 	"github.com/emersion/go-imap/responses"
 	"github.com/emersion/go-sasl"
 )
 
 var (
-	ErrAlreadyLoggedIn = errors.New("Already logged in")
+	ErrAlreadyLoggedIn   = errors.New("Already logged in")
 	ErrTLSAlreadyEnabled = errors.New("TLS is already enabled")
-	ErrLoginDisabled = errors.New("Login is disabled in current state")
+	ErrLoginDisabled     = errors.New("Login is disabled in current state")
 )
 
 // Check if the server supports STARTTLS.
-func  (c *Client) SupportsStartTLS() bool {
+func (c *Client) SupportsStartTLS() bool {
 	return c.Caps[imap.StartTLS]
 }
 
@@ -34,7 +34,7 @@ func (c *Client) StartTLS(tlsConfig *tls.Config) (err error) {
 
 	cmd := &commands.StartTLS{}
 
-	err = c.Upgrade(func (conn net.Conn) (net.Conn, error) {
+	err = c.Upgrade(func(conn net.Conn) (net.Conn, error) {
 		status, err := c.execute(cmd, nil)
 		if err != nil {
 			return nil, err
@@ -58,7 +58,7 @@ func (c *Client) StartTLS(tlsConfig *tls.Config) (err error) {
 
 // Check if the server supports a given authentication mechanism.
 func (c *Client) SupportsAuth(mech string) bool {
-	return c.Caps["AUTH=" + mech]
+	return c.Caps["AUTH="+mech]
 }
 
 // Indicates a SASL authentication mechanism to the server. If the server
@@ -80,9 +80,9 @@ func (c *Client) Authenticate(auth sasl.Client) (err error) {
 	}
 
 	res := &responses.Authenticate{
-		Mechanism: auth,
+		Mechanism:       auth,
 		InitialResponse: ir,
-		Writer: c.conn.Writer,
+		Writer:          c.conn.Writer,
 	}
 
 	status, err := c.execute(cmd, res)
diff --git a/client/cmd_selected.go b/client/cmd_selected.go
index 5531d1e..1f3c7b7 100644
--- a/client/cmd_selected.go
+++ b/client/cmd_selected.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"strings"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/commands"
 	"github.com/emersion/go-imap/responses"
 )
@@ -92,7 +92,7 @@ func (c *Client) search(uid bool, criteria *imap.SearchCriteria) (ids []uint32,
 
 	var cmd imap.Commander
 	cmd = &commands.Search{
-		Charset: "UTF-8",
+		Charset:  "UTF-8",
 		Criteria: criteria,
 	}
 	if uid {
@@ -140,7 +140,7 @@ func (c *Client) fetch(uid bool, seqset *imap.SeqSet, items []string, ch chan *i
 	var cmd imap.Commander
 	cmd = &commands.Fetch{
 		SeqSet: seqset,
-		Items: items,
+		Items:  items,
 	}
 	if uid {
 		cmd = &commands.Uid{Cmd: cmd}
@@ -170,7 +170,7 @@ func (c *Client) UidFetch(seqset *imap.SeqSet, items []string, ch chan *imap.Mes
 }
 
 func (c *Client) store(uid bool, seqset *imap.SeqSet, item string, value interface{}, ch chan *imap.Message) (err error) {
-	defer (func () {
+	defer (func() {
 		if ch != nil {
 			close(ch)
 		}
@@ -190,8 +190,8 @@ func (c *Client) store(uid bool, seqset *imap.SeqSet, item string, value interfa
 	var cmd imap.Commander
 	cmd = &commands.Store{
 		SeqSet: seqset,
-		Item: item,
-		Value: value,
+		Item:   item,
+		Value:  value,
 	}
 	if uid {
 		cmd = &commands.Uid{Cmd: cmd}
@@ -232,7 +232,7 @@ func (c *Client) copy(uid bool, seqset *imap.SeqSet, dest string) (err error) {
 
 	var cmd imap.Commander
 	cmd = &commands.Copy{
-		SeqSet: seqset,
+		SeqSet:  seqset,
 		Mailbox: dest,
 	}
 	if uid {
diff --git a/command.go b/command.go
new file mode 100644
index 0000000..109f7ab
--- /dev/null
+++ b/command.go
@@ -0,0 +1,91 @@
+package imap
+
+import (
+	"errors"
+	"strings"
+)
+
+// IMAP4rev1 commands.
+const (
+	Capability string = "CAPABILITY"
+	Noop              = "NOOP"
+	Logout            = "LOGOUT"
+	StartTLS          = "STARTTLS"
+
+	Authenticate = "AUTHENTICATE"
+	Login        = "LOGIN"
+
+	Select      = "SELECT"
+	Examine     = "EXAMINE"
+	Create      = "CREATE"
+	Delete      = "DELETE"
+	Rename      = "RENAME"
+	Subscribe   = "SUBSCRIBE"
+	Unsubscribe = "UNSUBSCRIBE"
+	List        = "LIST"
+	Lsub        = "LSUB"
+	Status      = "STATUS"
+	Append      = "APPEND"
+
+	Check   = "CHECK"
+	Close   = "CLOSE"
+	Expunge = "EXPUNGE"
+	Search  = "SEARCH"
+	Fetch   = "FETCH"
+	Store   = "STORE"
+	Copy    = "COPY"
+	Uid     = "UID"
+)
+
+// A value that can be converted to a command.
+type Commander interface {
+	Command() *Command
+}
+
+// A command.
+type Command struct {
+	// The command tag. It acts as a unique identifier for this command. If empty,
+	// the command is untagged.
+	Tag string
+	// The command name.
+	Name string
+	// The command arguments.
+	Arguments []interface{}
+}
+
+// Implements the Commander interface.
+func (cmd *Command) Command() *Command {
+	return cmd
+}
+
+func (cmd *Command) WriteTo(w Writer) error {
+	ww := w.writer()
+
+	tag := cmd.Tag
+	if tag == "" {
+		tag = "*"
+	}
+
+	fields := []interface{}{tag, cmd.Name}
+	fields = append(fields, cmd.Arguments...)
+	return ww.writeLine(fields...)
+}
+
+// Parse a command from fields.
+func (cmd *Command) Parse(fields []interface{}) error {
+	if len(fields) < 2 {
+		return errors.New("imap: cannot parse command: no enough fields")
+	}
+
+	var ok bool
+	if cmd.Tag, ok = fields[0].(string); !ok {
+		return errors.New("imap: cannot parse command: invalid tag")
+	}
+	if cmd.Name, ok = fields[1].(string); !ok {
+		return errors.New("imap: cannot parse command: invalid name")
+	}
+	cmd.Name = strings.ToUpper(cmd.Name) // Command names are case-insensitive
+
+	cmd.Arguments = fields[2:]
+	return nil
+}
diff --git a/commands/append.go b/commands/append.go
index cee3be1..8ca1b14 100644
--- a/commands/append.go
+++ b/commands/append.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"time"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
@@ -12,8 +12,8 @@ import (
 // See RFC 3501 section 6.3.11
 type Append struct {
 	Mailbox string
-	Flags []string
-	Date *time.Time
+	Flags   []string
+	Date    time.Time
 	Message *imap.Literal
 }
 
@@ -31,14 +31,14 @@ func (cmd *Append) Command() *imap.Command {
 		args = append(args, flags)
 	}
 
-	if cmd.Date != nil {
+	if !cmd.Date.IsZero() {
 		args = append(args, cmd.Date)
 	}
 
 	args = append(args, cmd.Message)
 
 	return &imap.Command{
-		Name: imap.Append,
+		Name:      imap.Append,
 		Arguments: args,
 	}
 }
@@ -81,7 +81,7 @@ func (cmd *Append) Parse(fields []interface{}) (err error) {
 			if !ok {
 				return errors.New("Date must be a string")
 			}
-			if cmd.Date, err = imap.ParseDate(date); err != nil {
+			if cmd.Date, err = imap.ParseDateTime(date); err != nil {
 				return err
 			}
 		}
diff --git a/commands/authenticate.go b/commands/authenticate.go
index f6614b9..07892d2 100644
--- a/commands/authenticate.go
+++ b/commands/authenticate.go
@@ -1,11 +1,13 @@
 package commands
 
 import (
+	"bufio"
 	"encoding/base64"
 	"errors"
+	"io"
 	"strings"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-sasl"
 )
 
@@ -17,7 +19,7 @@ type Authenticate struct {
 
 func (cmd *Authenticate) Command() *imap.Command {
 	return &imap.Command{
-		Name: imap.Authenticate,
+		Name:      imap.Authenticate,
 		Arguments: []interface{}{cmd.Mechanism},
 	}
 }
@@ -36,13 +38,15 @@ func (cmd *Authenticate) Parse(fields []interface{}) error {
 	return nil
 }
 
-func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, r *imap.Reader, w *imap.Writer) (err error) {
+func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, r io.Reader, w imap.Writer) (err error) {
 	sasl, ok := mechanisms[cmd.Mechanism]
 	if !ok {
 		err = errors.New("Unsupported mechanism")
 		return
 	}
 
+	scanner := bufio.NewScanner(r)
+
 	var response []byte
 	for {
 		var challenge []byte
@@ -57,13 +61,13 @@ func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, r *imap.Reade
 		if err = cont.WriteTo(w); err != nil {
 			return
 		}
-		if err = w.Flush(); err != nil {
-			return
-		}
 
-		if encoded, err = r.ReadInfo(); err != nil {
+		scanner.Scan()
+		if err = scanner.Err(); err != nil {
 			return
 		}
+
+		encoded = scanner.Text()
 		if encoded != "" {
 			response, err = base64.StdEncoding.DecodeString(encoded)
 			if err != nil {
diff --git a/commands/capability.go b/commands/capability.go
index f476cd4..3fcd89c 100644
--- a/commands/capability.go
+++ b/commands/capability.go
@@ -1,12 +1,12 @@
 package commands
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A CAPABILITY command.
 // See RFC 3501 section 6.1.1
-type Capability struct {}
+type Capability struct{}
 
 func (c *Capability) Command() *imap.Command {
 	return &imap.Command{
diff --git a/commands/check.go b/commands/check.go
index 46cd317..df39776 100644
--- a/commands/check.go
+++ b/commands/check.go
@@ -1,12 +1,12 @@
 package commands
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A CHECK command.
 // See RFC 3501 section 6.4.1
-type Check struct {}
+type Check struct{}
 
 func (cmd *Check) Command() *imap.Command {
 	return &imap.Command{
diff --git a/commands/close.go b/commands/close.go
index 883ee90..ed739e4 100644
--- a/commands/close.go
+++ b/commands/close.go
@@ -1,12 +1,12 @@
 package commands
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A CLOSE command.
 // See RFC 3501 section 6.4.2
-type Close struct {}
+type Close struct{}
 
 func (cmd *Close) Command() *imap.Command {
 	return &imap.Command{
diff --git a/commands/copy.go b/commands/copy.go
index 3d8890f..30c6d5e 100644
--- a/commands/copy.go
+++ b/commands/copy.go
@@ -3,14 +3,14 @@ package commands
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
 // A COPY command.
 // See RFC 3501 section 6.4.7
 type Copy struct {
-	SeqSet *imap.SeqSet
+	SeqSet  *imap.SeqSet
 	Mailbox string
 }
 
@@ -18,7 +18,7 @@ func (cmd *Copy) Command() *imap.Command {
 	mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
 
 	return &imap.Command{
-		Name: imap.Copy,
+		Name:      imap.Copy,
 		Arguments: []interface{}{cmd.SeqSet, mailbox},
 	}
 }
diff --git a/commands/create.go b/commands/create.go
index ee50be1..bbbce8e 100644
--- a/commands/create.go
+++ b/commands/create.go
@@ -3,7 +3,7 @@ package commands
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
@@ -17,7 +17,7 @@ func (cmd *Create) Command() *imap.Command {
 	mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
 
 	return &imap.Command{
-		Name: imap.Create,
+		Name:      imap.Create,
 		Arguments: []interface{}{mailbox},
 	}
 }
diff --git a/commands/delete.go b/commands/delete.go
index 0b315e0..f1f9126 100644
--- a/commands/delete.go
+++ b/commands/delete.go
@@ -3,7 +3,7 @@ package commands
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
@@ -17,7 +17,7 @@ func (cmd *Delete) Command() *imap.Command {
 	mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
 
 	return &imap.Command{
-		Name: imap.Delete,
+		Name:      imap.Delete,
 		Arguments: []interface{}{mailbox},
 	}
 }
diff --git a/commands/expunge.go b/commands/expunge.go
index 6ed10a7..4ee6c4a 100644
--- a/commands/expunge.go
+++ b/commands/expunge.go
@@ -1,12 +1,12 @@
 package commands
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // An EXPUNGE command.
 // See RFC 3501 section 6.4.3
-type Expunge struct {}
+type Expunge struct{}
 
 func (cmd *Expunge) Command() *imap.Command {
 	return &imap.Command{Name: imap.Expunge}
diff --git a/commands/fetch.go b/commands/fetch.go
index 5c06119..089f587 100644
--- a/commands/fetch.go
+++ b/commands/fetch.go
@@ -4,14 +4,14 @@ import (
 	"errors"
 	"strings"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A FETCH command.
 // See RFC 3501 section 6.4.5
 type Fetch struct {
 	SeqSet *imap.SeqSet
-	Items []string
+	Items  []string
 }
 
 func (cmd *Fetch) Command() *imap.Command {
@@ -21,7 +21,7 @@ func (cmd *Fetch) Command() *imap.Command {
 	}
 
 	return &imap.Command{
-		Name: imap.Fetch,
+		Name:      imap.Fetch,
 		Arguments: []interface{}{cmd.SeqSet, items},
 	}
 }
diff --git a/commands/list.go b/commands/list.go
index 7626862..7f474e4 100644
--- a/commands/list.go
+++ b/commands/list.go
@@ -3,7 +3,7 @@ package commands
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
@@ -12,7 +12,7 @@ import (
 // See RFC 3501 section 6.3.8
 type List struct {
 	Reference string
-	Mailbox string
+	Mailbox   string
 
 	Subscribed bool
 }
@@ -27,7 +27,7 @@ func (cmd *List) Command() *imap.Command {
 	mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
 
 	return &imap.Command{
-		Name: name,
+		Name:      name,
 		Arguments: []interface{}{ref, mailbox},
 	}
 }
diff --git a/commands/login.go b/commands/login.go
index 4d33712..3fa1238 100644
--- a/commands/login.go
+++ b/commands/login.go
@@ -3,7 +3,7 @@ package commands
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A LOGIN command.
@@ -15,7 +15,7 @@ type Login struct {
 
 func (cmd *Login) Command() *imap.Command {
 	return &imap.Command{
-		Name: imap.Login,
+		Name:      imap.Login,
 		Arguments: []interface{}{cmd.Username, cmd.Password},
 	}
 }
diff --git a/commands/logout.go b/commands/logout.go
index ac1eaf8..252b122 100644
--- a/commands/logout.go
+++ b/commands/logout.go
@@ -1,12 +1,12 @@
 package commands
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A LOGOUT command.
 // See RFC 3501 section 6.1.3
-type Logout struct {}
+type Logout struct{}
 
 func (c *Logout) Command() *imap.Command {
 	return &imap.Command{
diff --git a/commands/noop.go b/commands/noop.go
index e8129f8..14977ce 100644
--- a/commands/noop.go
+++ b/commands/noop.go
@@ -1,12 +1,12 @@
 package commands
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A NOOP command.
 // See RFC 3501 section 6.1.2
-type Noop struct {}
+type Noop struct{}
 
 func (c *Noop) Command() *imap.Command {
 	return &imap.Command{
diff --git a/commands/rename.go b/commands/rename.go
index 66076aa..61d7094 100644
--- a/commands/rename.go
+++ b/commands/rename.go
@@ -3,7 +3,7 @@ package commands
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
@@ -11,7 +11,7 @@ import (
 // See RFC 3501 section 6.3.5
 type Rename struct {
 	Existing string
-	New string
+	New      string
 }
 
 func (cmd *Rename) Command() *imap.Command {
@@ -19,7 +19,7 @@ func (cmd *Rename) Command() *imap.Command {
 	newName, _ := utf7.Encoder.String(cmd.New)
 
 	return &imap.Command{
-		Name: imap.Rename,
+		Name:      imap.Rename,
 		Arguments: []interface{}{existingName, newName},
 	}
 }
diff --git a/commands/search.go b/commands/search.go
index 0470343..4557ed5 100644
--- a/commands/search.go
+++ b/commands/search.go
@@ -3,13 +3,13 @@ package commands
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A SEARCH command.
 // See RFC 3501 section 6.4.4
 type Search struct {
-	Charset string
+	Charset  string
 	Criteria *imap.SearchCriteria
 }
 
@@ -21,7 +21,7 @@ func (cmd *Search) Command() *imap.Command {
 	args = append(args, cmd.Criteria.Format()...)
 
 	return &imap.Command{
-		Name: imap.Search,
+		Name:      imap.Search,
 		Arguments: args,
 	}
 }
diff --git a/commands/select.go b/commands/select.go
index 75ef808..d9a1c40 100644
--- a/commands/select.go
+++ b/commands/select.go
@@ -3,7 +3,7 @@ package commands
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
@@ -11,7 +11,7 @@ import (
 // If ReadOnly is set to true, the EXAMINE command will be used instead.
 // See RFC 3501 section 6.3.1
 type Select struct {
-	Mailbox string
+	Mailbox  string
 	ReadOnly bool
 }
 
@@ -24,7 +24,7 @@ func (cmd *Select) Command() *imap.Command {
 	mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
 
 	return &imap.Command{
-		Name: name,
+		Name:      name,
 		Arguments: []interface{}{mailbox},
 	}
 }
diff --git a/commands/starttls.go b/commands/starttls.go
index 4598896..cbc2127 100644
--- a/commands/starttls.go
+++ b/commands/starttls.go
@@ -1,12 +1,12 @@
 package commands
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A STARTTLS command.
 // See RFC 3501 section 6.2.1
-type StartTLS struct {}
+type StartTLS struct{}
 
 func (cmd *StartTLS) Command() *imap.Command {
 	return &imap.Command{
diff --git a/commands/status.go b/commands/status.go
index 8d4a392..7b8dcab 100644
--- a/commands/status.go
+++ b/commands/status.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"strings"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
@@ -12,7 +12,7 @@ import (
 // See RFC 3501 section 6.3.10
 type Status struct {
 	Mailbox string
-	Items []string
+	Items   []string
 }
 
 func (cmd *Status) Command() *imap.Command {
@@ -24,7 +24,7 @@ func (cmd *Status) Command() *imap.Command {
 	}
 
 	return &imap.Command{
-		Name: imap.Status,
+		Name:      imap.Status,
 		Arguments: []interface{}{mailbox, items},
 	}
 }
diff --git a/commands/store.go b/commands/store.go
index 7bcfa47..779480c 100644
--- a/commands/store.go
+++ b/commands/store.go
@@ -4,20 +4,20 @@ import (
 	"errors"
 	"strings"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A STORE command.
 // See RFC 3501 section 6.4.6
 type Store struct {
 	SeqSet *imap.SeqSet
-	Item string
-	Value interface{}
+	Item   string
+	Value  interface{}
 }
 
 func (cmd *Store) Command() *imap.Command {
 	return &imap.Command{
-		Name: imap.Store,
+		Name:      imap.Store,
 		Arguments: []interface{}{cmd.SeqSet, cmd.Item, cmd.Value},
 	}
 }
diff --git a/commands/subscribe.go b/commands/subscribe.go
index 85cf737..7149120 100644
--- a/commands/subscribe.go
+++ b/commands/subscribe.go
@@ -3,7 +3,7 @@ package commands
 import (
 	"errors"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
@@ -17,7 +17,7 @@ func (cmd *Subscribe) Command() *imap.Command {
 	mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
 
 	return &imap.Command{
-		Name: imap.Subscribe,
+		Name:      imap.Subscribe,
 		Arguments: []interface{}{mailbox},
 	}
 }
@@ -49,7 +49,7 @@ func (cmd *Unsubscribe) Command() *imap.Command {
 	mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
 
 	return &imap.Command{
-		Name: imap.Unsubscribe,
+		Name:      imap.Unsubscribe,
 		Arguments: []interface{}{mailbox},
 	}
 }
diff --git a/commands/uid.go b/commands/uid.go
index be549f3..12ae417 100644
--- a/commands/uid.go
+++ b/commands/uid.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"strings"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A UID command.
@@ -20,7 +20,7 @@ func (cmd *Uid) Command() *imap.Command {
 	args = append(args, inner.Arguments...)
 
 	return &imap.Command{
-		Name: imap.Uid,
+		Name:      imap.Uid,
 		Arguments: args,
 	}
 }
@@ -36,7 +36,7 @@ func (cmd *Uid) Parse(fields []interface{}) error {
 	}
 
 	cmd.Cmd = &imap.Command{
-		Name: strings.ToUpper(name), // Command names are case-insensitive
+		Name:      strings.ToUpper(name), // Command names are case-insensitive
 		Arguments: fields[1:],
 	}
 
diff --git a/common/command.go b/common/command.go
deleted file mode 100644
index 9e4cf8f..0000000
--- a/common/command.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package common
-
-import (
-	"errors"
-	"strings"
-)
-
-// IMAP4rev1 commands.
-const (
-	Capability string = "CAPABILITY"
-	Noop = "NOOP"
-	Logout = "LOGOUT"
-	StartTLS = "STARTTLS"
-
-	Authenticate = "AUTHENTICATE"
-	Login = "LOGIN"
-
-	Select = "SELECT"
-	Examine = "EXAMINE"
-	Create = "CREATE"
-	Delete = "DELETE"
-	Rename = "RENAME"
-	Subscribe = "SUBSCRIBE"
-	Unsubscribe = "UNSUBSCRIBE"
-	List = "LIST"
-	Lsub = "LSUB"
-	Status = "STATUS"
-	Append = "APPEND"
-
-	Check = "CHECK"
-	Close = "CLOSE"
-	Expunge = "EXPUNGE"
-	Search = "SEARCH"
-	Fetch = "FETCH"
-	Store = "STORE"
-	Copy = "COPY"
-	Uid = "UID"
-)
-
-// A command.
-type Command struct {
-	// The command tag. It acts as a unique identifier for this command.
-	Tag string
-	// The command name.
-	Name string
-	// The command arguments.
-	Arguments []interface{}
-}
-
-// Implements the Commander interface.
-func (cmd *Command) Command() *Command {
-	return cmd
-}
-
-func (cmd *Command) WriteTo(w *Writer) (N int64, err error) {
-	n, err := w.writeString(cmd.Tag + string(sp) + cmd.Name)
-	N += int64(n)
-	if err != nil {
-		return
-	}
-
-	if len(cmd.Arguments) > 0 {
-		n, err = w.WriteSp()
-		N += int64(n)
-		if err != nil {
-			return
-		}
-
-		n, err = w.WriteFields(cmd.Arguments)
-		N += int64(n)
-		if err != nil {
-			return
-		}
-	}
-
-	n, err = w.WriteCrlf()
-	N += int64(n)
-	return
-}
-
-// Parse a command from fields.
-func (cmd *Command) Parse(fields []interface{}) error {
-	if len(fields) < 2 {
-		return errors.New("Cannot parse command")
-	}
-
-	var ok bool
-	if cmd.Tag, ok = fields[0].(string); !ok {
-		return errors.New("Cannot parse command tag")
-	}
-	if cmd.Name, ok = fields[1].(string); !ok {
-		return errors.New("Cannot parse command name")
-	}
-
-	// Command names are case-insensitive
-	cmd.Name = strings.ToUpper(cmd.Name)
-
-	cmd.Arguments = fields[2:]
-
-	return nil
-}
-
-// A value that can be converted to a command.
-type Commander interface {
-	Command() *Command
-}
diff --git a/common/common.go b/common/common.go
deleted file mode 100644
index f96cbf8..0000000
--- a/common/common.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Generic structures and functions for IMAP.
-package common
-
-// An operation that will be applied on message flags.
-type FlagsOp string
-
-const (
-	// Replace existing flags by new ones
-	SetFlags FlagsOp = "FLAGS"
-	// Add new flags
-	AddFlags = "+FLAGS"
-	// Remove existing flags
-	RemoveFlags = "-FLAGS"
-)
-
-const SilentOp = ".SILENT"
diff --git a/common/write.go b/common/write.go
deleted file mode 100644
index ac47f54..0000000
--- a/common/write.go
+++ /dev/null
@@ -1,285 +0,0 @@
-package common
-
-import (
-	"errors"
-	"io"
-	"strconv"
-	"strings"
-	"time"
-	"unicode"
-)
-
-// A string that will be quoted.
-type Quoted string
-
-// A string writer.
-type StringWriter interface {
-	// WriteString writes a string. It returns the number of bytes written. If the
-	// count is less than len(s), it also returns an error explaining why the
-	// write is short.
-	// See https://golang.org/pkg/bufio/#writeString
-	WriteString(s string) (int, error)
-}
-
-// An interface implemented by net.Conn that allows to flush buffered data to
-// the remote.
-type Flusher interface {
-	// Write any buffered data to the underlying connection.
-	Flush() error
-}
-
-// Format an IMAP date.
-func FormatDate(date *time.Time) string {
-	return date.Format("2-Jan-2006 15:04:05 -0700")
-}
-
-// Convert a string list to a field list.
-func FormatStringList(list []string) (fields []interface{}) {
-	fields = make([]interface{}, len(list))
-	for i, v := range list {
-		fields[i] = v
-	}
-	return
-}
-
-// Check if a string is 8-bit clean.
-func isAscii(s string) bool {
-	for _, c := range s {
-		if c > unicode.MaxASCII || unicode.IsControl(c) {
-			return false
-		}
-	}
-	return true
-}
-
-type writer interface {
-	io.Writer
-}
-
-// An IMAP writer.
-type Writer struct {
-	writer
-
-	continues <-chan bool
-}
-
-func (w *Writer) writeString(s string) (int, error) {
-	return io.WriteString(w.writer, s)
-}
-
-// Write a separator.
-func (w *Writer) WriteSp() (int, error) {
-	return w.writeString(string(sp))
-}
-
-// Write a CRLF.
-func (w *Writer) WriteCrlf() (int, error) {
-	return w.writeString(string(cr) + string(lf))
-}
-
-// Write NIL.
-func (w *Writer) WriteNil() (int, error) {
-	return w.writeString("NIL")
-}
-
-func (w *Writer) WriteNumber(num uint32) (int, error) {
-	return w.writeString(strconv.Itoa(int(num)))
-}
-
-func (w *Writer) writeAtomString(s string) (int, error) {
-	return w.writeString(s)
-}
-
-func (w *Writer) writeQuotedString(s string) (int, error) {
-	return w.writeString(strconv.Quote(s))
-}
-
-func (w *Writer) WriteString(s string) (int, error) {
-	if !isAscii(s) {
-		// IMAP doesn't allow 8-bit data outside literals
-		return w.WriteLiteral(NewLiteral([]byte(s)))
-	}
-
-	specials := string([]rune{dquote, listStart, listEnd, literalStart, sp})
-	if strings.ToUpper(s) == "NIL" || s == "" || strings.ContainsAny(s, specials) {
-		return w.writeQuotedString(s)
-	}
-
-	return w.writeAtomString(s)
-}
-
-func (w *Writer) WriteDate(date *time.Time) (int, error) {
-	if date == nil {
-		return w.WriteNil()
-	}
-	return w.writeQuotedString(FormatDate(date))
-}
-
-func (w *Writer) WriteFields(fields []interface{}) (N int, err error) {
-	var n int
-
-	for i, field := range fields {
-		// Write separator
-		if i > 0 {
-			if n, err = w.WriteSp(); err != nil {
-				return
-			}
-			N += n
-		}
-
-		if field == nil {
-			n, err = w.WriteNil()
-		} else {
-			switch f := field.(type) {
-			case string:
-				n, err = w.WriteString(f)
-			case Quoted:
-				n, err = w.writeQuotedString(string(f))
-			case int:
-				n, err = w.WriteNumber(uint32(f))
-			case uint32:
-				n, err = w.WriteNumber(f)
-			case *Literal:
-				n, err = w.WriteLiteral(f)
-			case []interface{}:
-				n, err = w.WriteList(f)
-			case *time.Time:
-				n, err = w.WriteDate(f)
-			case *SeqSet:
-				n, err = w.writeString(f.String())
-			case *BodySectionName:
-				n, err = w.writeString(f.String())
-			default:
-				err = errors.New("Cannot format argument #" + strconv.Itoa(i))
-			}
-		}
-
-		N += n
-		if err != nil {
-			return
-		}
-	}
-
-	return
-}
-
-func (w *Writer) WriteList(fields []interface{}) (N int, err error) {
-	n, err := w.writeString(string(listStart))
-	N += n
-	if err != nil {
-		return
-	}
-
-	n, err = w.WriteFields(fields)
-	N += n
-	if err != nil {
-		return
-	}
-
-	n, err = w.writeString(string(listEnd))
-	N += n
-	return
-}
-
-func (w *Writer) writeLiteralField(literal *Literal) (N int, err error) {
-	field := string(literalStart) + strconv.Itoa(literal.Len()) + string(literalEnd)
-	n, err := w.writeString(field)
-	N += n
-	if err != nil {
-		return
-	}
-
-	n, err = w.WriteCrlf()
-	N += n
-	return
-}
-
-func (w *Writer) WriteLiteral(literal *Literal) (N int, err error) {
-	if literal == nil {
-		return w.WriteNil()
-	}
-
-	n, err := w.writeLiteralField(literal)
-	N += n
-	if err != nil {
-		return
-	}
-
-	// Make sure all buffered data is flushed because we'll maybe wait for a
-	// continuation request
-	if err = w.Flush(); err != nil {
-		return
-	}
-
-	// If a channel is available, wait for a continuation request before sending data
-	if w.continues != nil {
-		// Make sure to flush the writer, otherwise we may never receive a continuation request
-		if err = w.Flush(); err != nil {
-			return
-		}
-
-		if !<-w.continues {
-			err = errors.New("Cannot send literal: no continuation request received")
-			return
-		}
-	}
-
-	n, err = w.Write(literal.Bytes())
-	N += n
-	return
-}
-
-func (w *Writer) WriteRespCode(code string, args []interface{}) (N int, err error) {
-	n, err := w.writeString(string(respCodeStart))
-	if err != nil {
-		return
-	}
-	N += n
-
-	fields := []interface{}{code}
-	fields = append(fields, args...)
-
-	if n, err = w.WriteFields(fields); err != nil {
-		return
-	}
-	N += n
-
-	n, err = w.writeString(string(respCodeEnd))
-	N += n
-	return
-}
-
-func (w *Writer) WriteInfo(info string) (N int, err error) {
-	n, err := w.writeString(info)
-	if err != nil {
-		return
-	}
-	N += n
-
-	n, err = w.WriteCrlf()
-	if err != nil {
-		return
-	}
-	N += n
-
-	return
-}
-
-func (w *Writer) Flush() (err error) {
-	if f, ok := w.writer.(Flusher); ok {
-		err = f.Flush()
-	}
-	return
-}
-
-func NewWriter(w writer) *Writer {
-	return &Writer{writer: w}
-}
-
-func NewClientWriter(w writer, continues <-chan bool) *Writer {
-	return &Writer{writer: w, continues: continues}
-}
-
-type WriterTo interface {
-	WriteTo(w *Writer) error
-}
diff --git a/common/conn.go b/conn.go
similarity index 88%
rename from common/conn.go
rename to conn.go
index 527c8fe..d24a3df 100644
--- a/common/conn.go
+++ b/conn.go
@@ -1,9 +1,9 @@
-package common
+package imap
 
 import (
 	"bufio"
-	"net"
 	"io"
+	"net"
 	"os"
 )
 
@@ -29,7 +29,7 @@ const (
 	// In a selected state, a mailbox has been selected to access.
 	// This state is entered when a mailbox has been successfully
 	// selected.
-	SelectedState = AuthenticatedState + 1 << 2
+	SelectedState = AuthenticatedState + 1<<2
 
 	// In the logout state, the connection is being terminated. This
 	// state can be entered as a result of a client request (via the
@@ -51,7 +51,7 @@ type ConnUpgrader func(conn net.Conn) (net.Conn, error)
 type Conn struct {
 	net.Conn
 	*Reader
-	*Writer
+	Writer
 
 	waits chan struct{}
 
@@ -69,16 +69,23 @@ func (c *Conn) init() {
 	}
 
 	c.Reader.reader = bufio.NewReader(r)
-	c.Writer.writer = bufio.NewWriter(w)
+	c.writer().Writer = bufio.NewWriter(w)
+}
+
+func (c *Conn) Write(b []byte) (n int, err error) {
+	return c.Writer.Write(b)
 }
 
 // Write any buffered data to the underlying connection.
 func (c *Conn) Flush() (err error) {
-	if err = c.Writer.Flush(); err != nil {
+	if err = c.writer().Flush(); err != nil {
 		return
 	}
 
-	if f, ok := c.Conn.(Flusher); ok {
+	f, ok := c.Conn.(interface {
+		Flush() error
+	})
+	if ok {
 		if err = f.Flush(); err != nil {
 			return
 		}
@@ -118,7 +125,7 @@ func (c *Conn) SetDebug(debug bool) {
 }
 
 // Create a new IMAP connection.
-func NewConn(conn net.Conn, r *Reader, w *Writer) *Conn {
+func NewConn(conn net.Conn, r *Reader, w Writer) *Conn {
 	c := &Conn{Conn: conn, Reader: r, Writer: w}
 
 	c.init()
diff --git a/common/handle.go b/handle.go
similarity index 96%
rename from common/handle.go
rename to handle.go
index e4ae31b..72f815e 100644
--- a/common/handle.go
+++ b/handle.go
@@ -1,8 +1,8 @@
-package common
+package imap
 
 // A response that can be either accepted or rejected by a handler.
 type RespHandling struct {
-	Resp interface{}
+	Resp    interface{}
 	Accepts chan bool
 }
 
diff --git a/imap.go b/imap.go
index 947ec47..8b38b0a 100644
--- a/imap.go
+++ b/imap.go
@@ -1,3 +1,17 @@
 // An IMAP4rev1 (RFC 3501) library written in Go. It can be used to build a
 // client and/or a server and supports UTF-7.
 package imap
+
+// An operation that will be applied on message flags.
+type FlagsOp string
+
+const (
+	// Replace existing flags by new ones
+	SetFlags FlagsOp = "FLAGS"
+	// Add new flags
+	AddFlags = "+FLAGS"
+	// Remove existing flags
+	RemoveFlags = "-FLAGS"
+)
+
+const SilentOp = ".SILENT"
diff --git a/common/literal.go b/literal.go
similarity index 96%
rename from common/literal.go
rename to literal.go
index 7d39da0..0a5303f 100644
--- a/common/literal.go
+++ b/literal.go
@@ -1,4 +1,4 @@
-package common
+package imap
 
 // A literal.
 type Literal struct {
diff --git a/common/mailbox.go b/mailbox.go
similarity index 91%
rename from common/mailbox.go
rename to mailbox.go
index 707b7b0..5e939be 100644
--- a/common/mailbox.go
+++ b/mailbox.go
@@ -1,4 +1,4 @@
-package common
+package imap
 
 import (
 	"errors"
@@ -58,13 +58,13 @@ func (info *MailboxInfo) Format() []interface{} {
 
 // Mailbox status items.
 const (
-	MailboxFlags = "FLAGS"
+	MailboxFlags          = "FLAGS"
 	MailboxPermanentFlags = "PERMANENTFLAGS"
-	MailboxMessages = "MESSAGES"
-	MailboxRecent = "RECENT"
-	MailboxUnseen = "UNSEEN"
-	MailboxUidNext = "UIDNEXT"
-	MailboxUidValidity = "UIDVALIDITY"
+	MailboxMessages       = "MESSAGES"
+	MailboxRecent         = "RECENT"
+	MailboxUnseen         = "UNSEEN"
+	MailboxUidNext        = "UIDNEXT"
+	MailboxUidValidity    = "UIDVALIDITY"
 )
 
 // A mailbox status.
diff --git a/common/message.go b/message.go
similarity index 96%
rename from common/message.go
rename to message.go
index 8519fe6..73f2a07 100644
--- a/common/message.go
+++ b/message.go
@@ -1,4 +1,4 @@
-package common
+package imap
 
 import (
 	"bytes"
@@ -10,23 +10,14 @@ import (
 
 // Message flags, defined in RFC 3501 section 2.3.2.
 const (
-	SeenFlag = "\\Seen"
+	SeenFlag     = "\\Seen"
 	AnsweredFlag = "\\Answered"
-	FlaggedFlag = "\\Flagged"
-	DeletedFlag = "\\Deleted"
-	DraftFlag = "\\Draft"
-	RecentFlag = "\\Recent"
+	FlaggedFlag  = "\\Flagged"
+	DeletedFlag  = "\\Deleted"
+	DraftFlag    = "\\Draft"
+	RecentFlag   = "\\Recent"
 )
 
-// Parse an IMAP date.
-func ParseDate(date string) (*time.Time, error) {
-	t, err := time.Parse("2-Jan-2006 15:04:05 -0700", date)
-	if err != nil {
-		return nil, err
-	}
-	return &t, nil
-}
-
 // A message.
 type Message struct {
 	// The message sequence number. It must be greater than or equal to 1.
@@ -43,7 +34,7 @@ type Message struct {
 	// The message flags.
 	Flags []string
 	// The date the message was received by th server.
-	InternalDate *time.Time
+	InternalDate time.Time
 	// The message size.
 	Size uint32
 	// The message unique identifier. It must be greater than or equal to 1.
@@ -62,7 +53,7 @@ func (m *Message) Parse(fields []interface{}) error {
 
 	var key string
 	for i, f := range fields {
-		if i % 2 == 0 { // It's a key
+		if i%2 == 0 { // It's a key
 			var ok bool
 			if key, ok = f.(string); !ok {
 				return errors.New("Key is not a string")
@@ -103,7 +94,7 @@ func (m *Message) Parse(fields []interface{}) error {
 				}
 			case "INTERNALDATE":
 				date, _ := f.(string)
-				m.InternalDate, _ = ParseDate(date)
+				m.InternalDate, _ = ParseDateTime(date)
 			case "RFC822.SIZE":
 				m.Size, _ = ParseNumber(f)
 			case "UID":
@@ -228,7 +219,7 @@ func (section *BodySectionName) parse(s string) (err error) {
 	}
 
 	name := s[:partStart]
-	part := s[partStart+1:partEnd]
+	part := s[partStart+1 : partEnd]
 	partial := s[partEnd+1:]
 
 	if name == "BODY.PEEK" {
@@ -253,7 +244,7 @@ func (section *BodySectionName) parse(s string) (err error) {
 		if !strings.HasPrefix(partial, "<") || !strings.HasSuffix(partial, ">") {
 			return errors.New("Invalid body section name: invalid partial")
 		}
-		partial = partial[1:len(partial)-1]
+		partial = partial[1 : len(partial)-1]
 
 		partialParts := strings.SplitN(partial, ".", 2)
 		if len(partialParts) < 2 {
@@ -324,7 +315,7 @@ func (section *BodySectionName) ExtractPartial(b []byte) []byte {
 
 	from := section.Partial[0]
 	length := section.Partial[1]
-	to := from+length
+	to := from + length
 	if from > len(b) {
 		return nil
 	}
@@ -453,7 +444,7 @@ func (part *BodyPartName) String() (s string) {
 // See RFC 3501 page 77.
 type Envelope struct {
 	// The message date.
-	Date *time.Time
+	Date time.Time
 	// The message subject.
 	Subject string
 	// The From header addresses.
@@ -481,7 +472,7 @@ func (e *Envelope) Parse(fields []interface{}) error {
 	}
 
 	if date, ok := fields[0].(string); ok {
-		e.Date, _ = ParseDate(date)
+		e.Date, _ = ParseDateTime(date)
 	}
 	if subject, ok := fields[1].(string); ok {
 		e.Subject = subject
@@ -674,7 +665,7 @@ func ParseParamList(fields []interface{}) (params map[string]string, err error)
 			return
 		}
 
-		if i % 2 == 0 {
+		if i%2 == 0 {
 			key = p
 		} else {
 			params[key] = p
@@ -729,7 +720,7 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
 		bs.MimeSubType, _ = fields[end].(string)
 		end++
 
-		if len(fields) - end + 1 >= 4 { // Contains extension data
+		if len(fields)-end+1 >= 4 { // Contains extension data
 			bs.Extended = true
 
 			params, _ := fields[end].([]interface{})
@@ -768,7 +759,7 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
 
 		// Type-specific fields
 		if bs.MimeType == "message" && bs.MimeSubType == "rfc822" {
-			if len(fields) - end < 3 {
+			if len(fields)-end < 3 {
 				return errors.New("Missing type-specific fields for message/rfc822")
 			}
 
@@ -785,7 +776,7 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
 			end += 3
 		}
 		if bs.MimeType == "text" {
-			if len(fields) - end < 1 {
+			if len(fields)-end < 1 {
 				return errors.New("Missing type-specific fields for text/*")
 			}
 
@@ -793,7 +784,7 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
 			end++
 		}
 
-		if len(fields) - end + 1 >= 4 { // Contains extension data
+		if len(fields)-end+1 >= 4 { // Contains extension data
 			bs.Extended = true
 
 			bs.Md5, _ = fields[end].(string)
diff --git a/common/read.go b/read.go
similarity index 86%
rename from common/read.go
rename to read.go
index 863bc2a..8ae3cbb 100644
--- a/common/read.go
+++ b/read.go
@@ -1,30 +1,36 @@
-package common
+package imap
 
 import (
 	"errors"
 	"io"
 	"strconv"
 	"strings"
+	"time"
 )
 
 const (
-	sp = ' '
-	cr = '\r'
-	lf = '\n'
-	dquote = '"'
-	literalStart = '{'
-	literalEnd = '}'
-	listStart = '('
-	listEnd = ')'
+	sp            = ' '
+	cr            = '\r'
+	lf            = '\n'
+	dquote        = '"'
+	literalStart  = '{'
+	literalEnd    = '}'
+	listStart     = '('
+	listEnd       = ')'
 	respCodeStart = '['
-	respCodeEnd = ']'
+	respCodeEnd   = ']'
+)
+
+const (
+	crlf    = "\r\n"
+	nilAtom = "NIL"
 )
 
 // TODO: add CTL to atomSpecials
 var (
 	quotedSpecials = string([]rune{dquote, '\\'})
-	respSpecials = string([]rune{respCodeEnd})
-	atomSpecials = string([]rune{listStart, listEnd, literalStart, sp, '%', '*'}) + quotedSpecials + respSpecials
+	respSpecials   = string([]rune{respCodeEnd})
+	atomSpecials   = string([]rune{listStart, listEnd, literalStart, sp, '%', '*'}) + quotedSpecials + respSpecials
 )
 
 type parseError struct {
@@ -57,13 +63,13 @@ type reader interface {
 }
 
 // Convert a field to a number.
-func ParseNumber(input interface{}) (uint32, error) {
-	str, ok := input.(string)
+func ParseNumber(f interface{}) (uint32, error) {
+	s, ok := f.(string)
 	if !ok {
-		return 0, newParseError("Number is not an atom")
+		return 0, newParseError("imap: number is not a string")
 	}
 
-	nbr, err := strconv.ParseUint(str, 10, 32)
+	nbr, err := strconv.ParseUint(s, 10, 32)
 	if err != nil {
 		return 0, err
 	}
@@ -71,6 +77,26 @@ func ParseNumber(input interface{}) (uint32, error) {
 	return uint32(nbr), nil
 }
 
+func ParseDate(f interface{}) (t time.Time, err error) {
+	s, ok := f.(string)
+	if !ok {
+		err = newParseError("imap: date is not a string")
+		return
+	}
+
+	return time.Parse("2-Jan-2006", s)
+}
+
+func ParseDateTime(f interface{}) (t time.Time, err error) {
+	s, ok := f.(string)
+	if !ok {
+		err = newParseError("imap: date-time is not a string")
+		return
+	}
+
+	return time.Parse("2-Jan-2006 15:04:05 -0700", s)
+}
+
 // Convert a field list to a string list.
 func ParseStringList(fields []interface{}) ([]string, error) {
 	list := make([]string, len(fields))
@@ -91,9 +117,9 @@ func trimSuffix(str string, suffix rune) string {
 type Reader struct {
 	reader
 
-	brackets int
+	brackets   int
 	inRespCode bool
-	continues chan<- bool
+	continues  chan<- bool
 }
 
 func (r *Reader) ReadSp() error {
diff --git a/common/response.go b/response.go
similarity index 82%
rename from common/response.go
rename to response.go
index 203fc48..b50490a 100644
--- a/common/response.go
+++ b/response.go
@@ -1,20 +1,25 @@
-package common
+package imap
 
 import (
 	"errors"
 )
 
+// A value that can be converted to a Resp.
+type Responser interface {
+	Response() *Resp
+}
+
 // A response.
 // See RFC 3501 section 2.2.2
 type Resp struct {
-	// The response tag. Can be either * for untagged responses, + for continuation
+	// The response tag. Can be either "" for untagged responses, "+" for continuation
 	// requests or a previous command's tag.
 	Tag string
 	// The parsed response fields.
 	Fields []interface{}
 }
 
-func (r *Resp) WriteTo(w *Writer) (err error) {
+func (r *Resp) WriteTo(w Writer) error {
 	tag := r.Tag
 	if tag == "" {
 		tag = "*"
@@ -22,19 +27,13 @@ func (r *Resp) WriteTo(w *Writer) (err error) {
 
 	fields := []interface{}{tag}
 	fields = append(fields, r.Fields...)
-
-	if _, err = w.WriteFields(fields); err != nil {
-		return
-	}
-
-	_, err = w.WriteCrlf()
-	return
+	return w.writer().writeLine(fields...)
 }
 
 // Create a new untagged response.
 func NewUntaggedResp(fields []interface{}) *Resp {
 	return &Resp{
-		Tag: "*",
+		Tag:    "*",
 		Fields: fields,
 	}
 }
@@ -45,26 +44,20 @@ type ContinuationResp struct {
 	Info string
 }
 
-func (r *ContinuationResp) WriteTo(w *Writer) (err error) {
-	if _, err = w.WriteString("+"); err != nil {
-		return
+func (r *ContinuationResp) WriteTo(w Writer) error {
+	ww := w.writer()
+
+	if err := ww.writeString("+"); err != nil {
+		return err
 	}
 
 	if r.Info != "" {
-		if _, err = w.WriteSp(); err != nil {
-			return
+		if err := ww.writeString(string(sp) + r.Info); err != nil {
+			return err
 		}
-
-		_, err = w.WriteInfo(r.Info)
-	} else {
-		_, err = w.WriteCrlf()
 	}
-	return
-}
 
-// A value that can be converted to a Resp.
-type Responser interface {
-	Response() *Resp
+	return ww.writeCrlf()
 }
 
 // Read a single response from a Reader. Returns either a continuation request,
@@ -114,7 +107,7 @@ func ReadResp(r *Reader) (out interface{}, err error) {
 					isStatus = true
 
 					res := &StatusResp{
-						Tag: tag,
+						Tag:  tag,
 						Type: status,
 					}
 
diff --git a/responses/authenticate.go b/responses/authenticate.go
index 5316451..f384911 100644
--- a/responses/authenticate.go
+++ b/responses/authenticate.go
@@ -3,25 +3,24 @@ package responses
 import (
 	"encoding/base64"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-sasl"
 )
 
 // An AUTHENTICATE response.
 type Authenticate struct {
-	Mechanism sasl.Client
+	Mechanism       sasl.Client
 	InitialResponse []byte
-	Writer *imap.Writer
+	Writer          imap.Writer
 }
 
 func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) {
 	w := r.Writer
 
 	// Cancel auth if an error occurs
-	defer (func () {
+	defer (func() {
 		if err != nil {
-			w.WriteString("*")
-			w.WriteCrlf()
+			w.Write([]byte("*\r\n"))
 		}
 	})()
 
@@ -36,10 +35,7 @@ func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) {
 		// Empty challenge, send initial response as stated in RFC 2222 section 5.1
 		if cont.Info == "" && r.InitialResponse != nil {
 			encoded := base64.StdEncoding.EncodeToString(r.InitialResponse)
-			if _, err = w.WriteString(encoded); err != nil {
-				return
-			}
-			if _, err = w.WriteCrlf(); err != nil {
+			if _, err = w.Write([]byte(encoded + "\r\n")); err != nil {
 				return
 			}
 			if err = w.Flush(); err != nil {
@@ -63,10 +59,7 @@ func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) {
 		}
 
 		encoded := base64.StdEncoding.EncodeToString(res)
-		if _, err = w.WriteString(encoded); err != nil {
-			return
-		}
-		if _, err = w.WriteCrlf(); err != nil {
+		if _, err = w.Write([]byte(encoded + "\r\n")); err != nil {
 			return
 		}
 		if err = w.Flush(); err != nil {
diff --git a/responses/capability.go b/responses/capability.go
index af9481a..4865077 100644
--- a/responses/capability.go
+++ b/responses/capability.go
@@ -1,7 +1,7 @@
 package responses
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A CAPABILITY response.
@@ -10,16 +10,6 @@ type Capability struct {
 	Caps []string
 }
 
-func (r *Capability) WriteTo(w *imap.Writer) error {
-	fields := []interface{}{imap.Capability}
-	for _, cap := range r.Caps {
-		fields = append(fields, cap)
-	}
-
-	res := &imap.Resp{Fields: fields}
-	return res.WriteTo(w)
-}
-
 func (r *Capability) HandleFrom(hdlr imap.RespHandler) (err error) {
 	for h := range hdlr {
 		caps, ok := h.AcceptNamedResp(imap.Capability)
@@ -37,3 +27,13 @@ func (r *Capability) HandleFrom(hdlr imap.RespHandler) (err error) {
 
 	return
 }
+
+func (r *Capability) WriteTo(w imap.Writer) error {
+	fields := []interface{}{imap.Capability}
+	for _, cap := range r.Caps {
+		fields = append(fields, cap)
+	}
+
+	res := &imap.Resp{Fields: fields}
+	return res.WriteTo(w)
+}
diff --git a/responses/expunge.go b/responses/expunge.go
index e247b75..bd58d61 100644
--- a/responses/expunge.go
+++ b/responses/expunge.go
@@ -1,7 +1,7 @@
 package responses
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // An EXPUNGE response.
@@ -30,7 +30,7 @@ func (r *Expunge) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *Expunge) WriteTo(w *imap.Writer) error {
+func (r *Expunge) WriteTo(w imap.Writer) error {
 	for seqNum := range r.SeqNums {
 		res := imap.NewUntaggedResp([]interface{}{seqNum, imap.Expunge})
 
diff --git a/responses/fetch.go b/responses/fetch.go
index e0ab700..3b4923e 100644
--- a/responses/fetch.go
+++ b/responses/fetch.go
@@ -1,7 +1,7 @@
 package responses
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A FETCH response.
@@ -40,7 +40,7 @@ func (r *Fetch) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *Fetch) WriteTo(w *imap.Writer) error {
+func (r *Fetch) WriteTo(w imap.Writer) error {
 	for msg := range r.Messages {
 		res := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.Fetch, msg.Format()})
 
diff --git a/responses/list.go b/responses/list.go
index 76bd667..84a556d 100644
--- a/responses/list.go
+++ b/responses/list.go
@@ -1,14 +1,14 @@
 package responses
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A LIST response.
 // If Subscribed is set to true, LSUB will be used instead.
 // See RFC 3501 section 7.2.2
 type List struct {
-	Mailboxes chan *imap.MailboxInfo
+	Mailboxes  chan *imap.MailboxInfo
 	Subscribed bool
 }
 
@@ -40,7 +40,7 @@ func (r *List) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *List) WriteTo(w *imap.Writer) (err error) {
+func (r *List) WriteTo(w imap.Writer) (err error) {
 	name := r.Name()
 
 	for mbox := range r.Mailboxes {
diff --git a/responses/search.go b/responses/search.go
index 98340b6..e62688a 100644
--- a/responses/search.go
+++ b/responses/search.go
@@ -1,7 +1,7 @@
 package responses
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A SEARCH response.
@@ -26,7 +26,7 @@ func (r *Search) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *Search) WriteTo(w *imap.Writer) (err error) {
+func (r *Search) WriteTo(w imap.Writer) (err error) {
 	fields := []interface{}{imap.Search}
 	for _, id := range r.Ids {
 		fields = append(fields, id)
diff --git a/responses/select.go b/responses/select.go
index 74b11af..0b7bcd0 100644
--- a/responses/select.go
+++ b/responses/select.go
@@ -1,7 +1,7 @@
 package responses
 
 import (
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 // A SELECT response.
@@ -58,7 +58,7 @@ func (r *Select) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *Select) WriteTo(w *imap.Writer) (err error) {
+func (r *Select) WriteTo(w imap.Writer) (err error) {
 	status := r.Mailbox
 
 	for _, item := range r.Mailbox.Items {
@@ -78,10 +78,10 @@ func (r *Select) WriteTo(w *imap.Writer) (err error) {
 				flags[i] = f
 			}
 			statusRes := &imap.StatusResp{
-				Type: imap.StatusOk,
-				Code: imap.CodePermanentFlags,
+				Type:      imap.StatusOk,
+				Code:      imap.CodePermanentFlags,
 				Arguments: []interface{}{flags},
-				Info: "Flags permitted.",
+				Info:      "Flags permitted.",
 			}
 			if err = statusRes.WriteTo(w); err != nil {
 				return
@@ -98,8 +98,8 @@ func (r *Select) WriteTo(w *imap.Writer) (err error) {
 			}
 		case imap.MailboxUnseen:
 			statusRes := &imap.StatusResp{
-				Type: imap.StatusOk,
-				Code: imap.CodeUnseen,
+				Type:      imap.StatusOk,
+				Code:      imap.CodeUnseen,
 				Arguments: []interface{}{status.Unseen},
 			}
 			if err = statusRes.WriteTo(w); err != nil {
@@ -107,20 +107,20 @@ func (r *Select) WriteTo(w *imap.Writer) (err error) {
 			}
 		case imap.MailboxUidNext:
 			statusRes := &imap.StatusResp{
-				Type: imap.StatusOk,
-				Code: imap.CodeUidNext,
+				Type:      imap.StatusOk,
+				Code:      imap.CodeUidNext,
 				Arguments: []interface{}{status.UidNext},
-				Info: "Predicted next UID",
+				Info:      "Predicted next UID",
 			}
 			if err = statusRes.WriteTo(w); err != nil {
 				return
 			}
 		case imap.MailboxUidValidity:
 			statusRes := &imap.StatusResp{
-				Type: imap.StatusOk,
-				Code: imap.CodeUidValidity,
+				Type:      imap.StatusOk,
+				Code:      imap.CodeUidValidity,
 				Arguments: []interface{}{status.UidValidity},
-				Info: "UIDs valid",
+				Info:      "UIDs valid",
 			}
 			if err = statusRes.WriteTo(w); err != nil {
 				return
diff --git a/responses/status.go b/responses/status.go
index 7253c5d..f704dc9 100644
--- a/responses/status.go
+++ b/responses/status.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"strings"
 
-	imap "github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/utf7"
 )
 
@@ -43,7 +43,7 @@ func (r *Status) HandleFrom(hdlr imap.RespHandler) error {
 
 		var key string
 		for i, f := range items {
-			if i % 2 == 0 {
+			if i%2 == 0 {
 				var ok bool
 				if key, ok = f.(string); !ok {
 					return errors.New("Key is not a string")
@@ -71,7 +71,7 @@ func (r *Status) HandleFrom(hdlr imap.RespHandler) error {
 	return nil
 }
 
-func (r *Status) WriteTo(w *imap.Writer) error {
+func (r *Status) WriteTo(w imap.Writer) error {
 	mbox := r.Mailbox
 
 	var fields []interface{}
diff --git a/common/search.go b/search.go
similarity index 71%
rename from common/search.go
rename to search.go
index 265c700..9487075 100644
--- a/common/search.go
+++ b/search.go
@@ -1,4 +1,4 @@
-package common
+package imap
 
 import (
 	"errors"
@@ -6,59 +6,46 @@ import (
 	"time"
 )
 
-const searchDateLayout = "2-Jan-2006"
-
-// Parse a date in the search format.
-func ParseSearchDate(s string) (*time.Time, error) {
-	t, err := time.Parse(searchDateLayout, s)
-	return &t, err
-}
-
-// Format a date to the search format.
-func FormatSearchDate(t *time.Time) string {
-	return t.Format(searchDateLayout)
-}
-
 // TODO: support AND with same fields (e.g. BCC mickey BCC mouse)
 
 // A search criteria.
 // See RFC 3501 section 6.4.4 for a description of each field.
 type SearchCriteria struct {
-	SeqSet *SeqSet
-	Answered bool
-	Bcc string
-	Before *time.Time
-	Body string
-	Cc string
-	Deleted bool
-	Draft bool
-	Flagged bool
-	From string
-	Header [2]string
-	Keyword string
-	Larger uint32
-	New bool
-	Not *SearchCriteria
-	Old bool
-	On *time.Time
-	Or [2]*SearchCriteria
-	Recent bool
-	Seen bool
-	SentBefore *time.Time
-	SentOn *time.Time
-	SentSince *time.Time
-	Since *time.Time
-	Smaller uint32
-	Subject string
-	Text string
-	To string
-	Uid *SeqSet
+	SeqSet     *SeqSet
+	Answered   bool
+	Bcc        string
+	Before     time.Time
+	Body       string
+	Cc         string
+	Deleted    bool
+	Draft      bool
+	Flagged    bool
+	From       string
+	Header     [2]string
+	Keyword    string
+	Larger     uint32
+	New        bool
+	Not        *SearchCriteria
+	Old        bool
+	On         time.Time
+	Or         [2]*SearchCriteria
+	Recent     bool
+	Seen       bool
+	SentBefore time.Time
+	SentOn     time.Time
+	SentSince  time.Time
+	Since      time.Time
+	Smaller    uint32
+	Subject    string
+	Text       string
+	To         string
+	Uid        *SeqSet
 	Unanswered bool
-	Undeleted bool
-	Undraft bool
-	Unflagged bool
-	Unkeyword string
-	Unseen bool
+	Undeleted  bool
+	Undraft    bool
+	Unflagged  bool
+	Unkeyword  string
+	Unseen     bool
 }
 
 // Parse search criteria from fields.
@@ -81,9 +68,7 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 			c.Bcc, _ = fields[i].(string)
 		case "BEFORE":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.Before, _ = ParseSearchDate(date)
-			}
+			c.Before, _ = ParseDate(fields[i])
 		case "BODY":
 			i++
 			c.Body, _ = fields[i].(string)
@@ -126,9 +111,7 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 			c.Old = true
 		case "ON":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.On, _ = ParseSearchDate(date)
-			}
+			c.On, _ = ParseDate(fields[i])
 		case "OR":
 			i++
 			leftFields, _ := fields[i].([]interface{})
@@ -149,24 +132,16 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 			c.Seen = true
 		case "SENTBEFORE":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.SentBefore, _ = ParseSearchDate(date)
-			}
+			c.SentBefore, _ = ParseDate(fields[i])
 		case "SENTON":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.SentOn, _ = ParseSearchDate(date)
-			}
+			c.SentOn, _ = ParseDate(fields[i])
 		case "SENTSINCE":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.SentSince, _ = ParseSearchDate(date)
-			}
+			c.SentSince, _ = ParseDate(fields[i])
 		case "SINCE":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.Since, _ = ParseSearchDate(date)
-			}
+			c.Since, _ = ParseDate(fields[i])
 		case "SMALLER":
 			i++
 			c.Smaller, _ = ParseNumber(fields[i])
@@ -220,8 +195,8 @@ func (c *SearchCriteria) Format() (fields []interface{}) {
 	if c.Bcc != "" {
 		fields = append(fields, "BCC", c.Bcc)
 	}
-	if c.Before != nil {
-		fields = append(fields, "BEFORE", FormatSearchDate(c.Before))
+	if !c.Before.IsZero() {
+		fields = append(fields, "BEFORE", FormatDate(c.Before))
 	}
 	if c.Body != "" {
 		fields = append(fields, "BODY", c.Body)
@@ -259,8 +234,8 @@ func (c *SearchCriteria) Format() (fields []interface{}) {
 	if c.Old {
 		fields = append(fields, "OLD")
 	}
-	if c.On != nil {
-		fields = append(fields, "ON", FormatSearchDate(c.On))
+	if !c.On.IsZero() {
+		fields = append(fields, "ON", FormatDate(c.On))
 	}
 	if c.Or[0] != nil && c.Or[1] != nil {
 		fields = append(fields, "OR", c.Or[0].Format(), c.Or[1].Format())
@@ -271,17 +246,17 @@ func (c *SearchCriteria) Format() (fields []interface{}) {
 	if c.Seen {
 		fields = append(fields, "SEEN")
 	}
-	if c.SentBefore != nil {
-		fields = append(fields, "SENTBEFORE", FormatSearchDate(c.SentBefore))
+	if !c.SentBefore.IsZero() {
+		fields = append(fields, "SENTBEFORE", FormatDate(c.SentBefore))
 	}
-	if c.SentOn != nil {
-		fields = append(fields, "SENTON", FormatSearchDate(c.SentOn))
+	if !c.SentOn.IsZero() {
+		fields = append(fields, "SENTON", FormatDate(c.SentOn))
 	}
-	if c.SentSince != nil {
-		fields = append(fields, "SENTSINCE", FormatSearchDate(c.SentSince))
+	if !c.SentSince.IsZero() {
+		fields = append(fields, "SENTSINCE", FormatDate(c.SentSince))
 	}
-	if c.Since != nil {
-		fields = append(fields, "SINCE", FormatSearchDate(c.Since))
+	if !c.Since.IsZero() {
+		fields = append(fields, "SINCE", FormatDate(c.Since))
 	}
 	if c.Smaller != 0 {
 		fields = append(fields, "SMALLER", c.Smaller)
diff --git a/common/seqset.go b/seqset.go
similarity index 99%
rename from common/seqset.go
rename to seqset.go
index ca351cf..7313bb1 100644
--- a/common/seqset.go
+++ b/seqset.go
@@ -1,4 +1,4 @@
-package common
+package imap
 
 import (
 	"fmt"
diff --git a/server/cmd_any.go b/server/cmd_any.go
index 2538dff..cdfb300 100644
--- a/server/cmd_any.go
+++ b/server/cmd_any.go
@@ -1,8 +1,8 @@
 package server
 
 import (
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/backend"
-	"github.com/emersion/go-imap/common"
 	"github.com/emersion/go-imap/commands"
 	"github.com/emersion/go-imap/responses"
 )
@@ -11,8 +11,8 @@ type Capability struct {
 	commands.Capability
 }
 
-func (cmd *Capability) Handle(conn *Conn) error {
-	res := &responses.Capability{Caps: conn.getCaps()}
+func (cmd *Capability) Handle(conn Conn) error {
+	res := &responses.Capability{Caps: conn.Capabilities()}
 	return conn.WriteResp(res)
 }
 
@@ -20,10 +20,11 @@ type Noop struct {
 	commands.Noop
 }
 
-func (cmd *Noop) Handle(conn *Conn) error {
-	if conn.Mailbox != nil {
+func (cmd *Noop) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.Mailbox != nil {
 		// If a mailbox is selected, NOOP can be used to poll for server updates
-		if mbox, ok := conn.Mailbox.(backend.UpdaterMailbox); ok {
+		if mbox, ok := ctx.Mailbox.(backend.UpdaterMailbox); ok {
 			return mbox.Poll()
 		}
 	}
@@ -35,9 +36,9 @@ type Logout struct {
 	commands.Logout
 }
 
-func (cmd *Logout) Handle(conn *Conn) error {
-	res := &common.StatusResp{
-		Type: common.StatusBye,
+func (cmd *Logout) Handle(conn Conn) error {
+	res := &imap.StatusResp{
+		Type: imap.StatusBye,
 		Info: "Closing connection",
 	}
 
@@ -46,6 +47,6 @@ func (cmd *Logout) Handle(conn *Conn) error {
 	}
 
 	// Request to close the connection
-	conn.State = common.LogoutState
+	conn.Context().State = imap.LogoutState
 	return nil
 }
diff --git a/server/cmd_auth.go b/server/cmd_auth.go
index 3313d8f..dc5f6f9 100644
--- a/server/cmd_auth.go
+++ b/server/cmd_auth.go
@@ -4,12 +4,12 @@ import (
 	"errors"
 	"strings"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/commands"
 	"github.com/emersion/go-imap/responses"
 )
 
-// Common errors in Authenticated state.
+// imap errors in Authenticated state.
 var (
 	ErrNotAuthenticated = errors.New("Not authenticated")
 )
@@ -18,20 +18,21 @@ type Select struct {
 	commands.Select
 }
 
-func (cmd *Select) Handle(conn *Conn) error {
-	if conn.User == nil {
+func (cmd *Select) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
+	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		return err
 	}
 
 	items := []string{
-		common.MailboxFlags, common.MailboxPermanentFlags,
-		common.MailboxMessages, common.MailboxRecent, common.MailboxUnseen,
-		common.MailboxUidNext, common.MailboxUidValidity,
+		imap.MailboxFlags, imap.MailboxPermanentFlags,
+		imap.MailboxMessages, imap.MailboxRecent, imap.MailboxUnseen,
+		imap.MailboxUidNext, imap.MailboxUidValidity,
 	}
 
 	status, err := mbox.Status(items)
@@ -39,20 +40,20 @@ func (cmd *Select) Handle(conn *Conn) error {
 		return err
 	}
 
-	conn.Mailbox = mbox
-	conn.MailboxReadOnly = cmd.ReadOnly || status.ReadOnly
+	ctx.Mailbox = mbox
+	ctx.MailboxReadOnly = cmd.ReadOnly || status.ReadOnly
 
 	res := &responses.Select{Mailbox: status}
 	if err := conn.WriteResp(res); err != nil {
 		return err
 	}
 
-	code := common.CodeReadWrite
-	if conn.MailboxReadOnly {
-		code = common.CodeReadOnly
+	code := imap.CodeReadWrite
+	if ctx.MailboxReadOnly {
+		code = imap.CodeReadOnly
 	}
-	return ErrStatusResp(&common.StatusResp{
-		Type: common.StatusOk,
+	return ErrStatusResp(&imap.StatusResp{
+		Type: imap.StatusOk,
 		Code: code,
 	})
 }
@@ -61,48 +62,52 @@ type Create struct {
 	commands.Create
 }
 
-func (cmd *Create) Handle(conn *Conn) error {
-	if conn.User == nil {
+func (cmd *Create) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	return conn.User.CreateMailbox(cmd.Mailbox)
+	return ctx.User.CreateMailbox(cmd.Mailbox)
 }
 
 type Delete struct {
 	commands.Delete
 }
 
-func (cmd *Delete) Handle(conn *Conn) error {
-	if conn.User == nil {
+func (cmd *Delete) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	return conn.User.DeleteMailbox(cmd.Mailbox)
+	return ctx.User.DeleteMailbox(cmd.Mailbox)
 }
 
 type Rename struct {
 	commands.Rename
 }
 
-func (cmd *Rename) Handle(conn *Conn) error {
-	if conn.User == nil {
+func (cmd *Rename) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	return conn.User.RenameMailbox(cmd.Existing, cmd.New)
+	return ctx.User.RenameMailbox(cmd.Existing, cmd.New)
 }
 
 type Subscribe struct {
 	commands.Subscribe
 }
 
-func (cmd *Subscribe) Handle(conn *Conn) error {
-	if conn.User == nil {
+func (cmd *Subscribe) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
+	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		return err
 	}
@@ -114,12 +119,13 @@ type Unsubscribe struct {
 	commands.Unsubscribe
 }
 
-func (cmd *Unsubscribe) Handle(conn *Conn) error {
-	if conn.User == nil {
+func (cmd *Unsubscribe) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
+	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		return err
 	}
@@ -131,22 +137,23 @@ type List struct {
 	commands.List
 }
 
-func (cmd *List) Handle(conn *Conn) error {
-	if conn.User == nil {
+func (cmd *List) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.User == nil {
 		return ErrNotAuthenticated
 	}
 
 	done := make(chan error, 1)
 
-	ch := make(chan *common.MailboxInfo)
+	ch := make(chan *imap.MailboxInfo)
 	res := &responses.List{Mailboxes: ch, Subscribed: cmd.Subscribed}
 
-	go (func () {
+	go (func() {
 		done <- conn.WriteResp(res)
 		close(done)
 	})()
 
-	mailboxes, err := conn.User.ListMailboxes(cmd.Subscribed)
+	mailboxes, err := ctx.User.ListMailboxes(cmd.Subscribed)
 	if err != nil {
 		close(ch)
 		return err
@@ -187,12 +194,13 @@ type Status struct {
 	commands.Status
 }
 
-func (cmd *Status) Handle(conn *Conn) error {
-	if conn.User == nil {
+func (cmd *Status) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
+	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		return err
 	}
@@ -210,12 +218,13 @@ type Append struct {
 	commands.Append
 }
 
-func (cmd *Append) Handle(conn *Conn) error {
-	if conn.User == nil {
+func (cmd *Append) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
+	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		// TODO: add [TRYCREATE] to the NO response
 		return err
@@ -227,8 +236,8 @@ func (cmd *Append) Handle(conn *Conn) error {
 
 	// If APPEND targets the currently selected mailbox, send an untagged EXISTS
 	// Do this only if the backend doesn't send updates itself
-	if conn.Server.Updates == nil && conn.Mailbox != nil && conn.Mailbox.Name() == mbox.Name() {
-		status, err := mbox.Status([]string{common.MailboxMessages})
+	if conn.Server().Updates == nil && ctx.Mailbox != nil && ctx.Mailbox.Name() == mbox.Name() {
+		status, err := mbox.Status([]string{imap.MailboxMessages})
 		if err != nil {
 			return err
 		}
diff --git a/server/cmd_noauth.go b/server/cmd_noauth.go
index 07cff97..2e2ca3f 100644
--- a/server/cmd_noauth.go
+++ b/server/cmd_noauth.go
@@ -5,86 +5,98 @@ import (
 	"errors"
 	"net"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/commands"
 	"github.com/emersion/go-imap/responses"
 	"github.com/emersion/go-sasl"
 )
 
-// Common errors in Not Authenticated state.
+// imap errors in Not Authenticated state.
 var (
 	ErrAlreadyAuthenticated = errors.New("Already authenticated")
-	ErrAuthDisabled = errors.New("Authentication disabled")
+	ErrAuthDisabled         = errors.New("Authentication disabled")
 )
 
 type StartTLS struct {
 	commands.StartTLS
 }
 
-func (cmd *StartTLS) Handle(conn *Conn) error {
-	if conn.State != common.NotAuthenticatedState {
+func (cmd *StartTLS) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.State != imap.NotAuthenticatedState {
 		return ErrAlreadyAuthenticated
 	}
 	if conn.IsTLS() {
 		return errors.New("TLS is already enabled")
 	}
-	if conn.Server.TLSConfig == nil {
+	if conn.Server().TLSConfig == nil {
 		return errors.New("TLS support not enabled")
 	}
 
 	// Send an OK status response to let the client know that the TLS handshake
 	// can begin
-	return ErrStatusResp(&common.StatusResp{
-		Type: common.StatusOk,
+	return ErrStatusResp(&imap.StatusResp{
+		Type: imap.StatusOk,
 		Info: "Begin TLS negotiation now",
 	})
 }
 
-func (cmd *StartTLS) Upgrade(conn *Conn) error {
-	tlsConfig := conn.Server.TLSConfig
+func (cmd *StartTLS) Upgrade(conn Conn) error {
+	tlsConfig := conn.Server().TLSConfig
 
-	err := conn.Upgrade(func (conn net.Conn) (net.Conn, error) {
-		upgraded := tls.Server(conn, tlsConfig)
-		err := upgraded.Handshake()
-		return upgraded, err
+	var tlsConn *tls.Conn
+	err := conn.Upgrade(func(conn net.Conn) (net.Conn, error) {
+		tlsConn = tls.Server(conn, tlsConfig)
+		err := tlsConn.Handshake()
+		return tlsConn, err
 	})
 	if err != nil {
 		return err
 	}
 
-	conn.isTLS = true
+	conn.setTLSConn(tlsConn)
 
-	res := &responses.Capability{Caps: conn.getCaps()}
+	res := &responses.Capability{Caps: conn.Capabilities()}
 	return conn.WriteResp(res)
 }
 
-func afterAuthStatus(conn *Conn) error {
-	return ErrStatusResp(&common.StatusResp{
-		Type: common.StatusOk,
-		Code: common.CodeCapability,
-		Arguments: common.FormatStringList(conn.getCaps()),
+func afterAuthStatus(conn Conn) error {
+	return ErrStatusResp(&imap.StatusResp{
+		Type:      imap.StatusOk,
+		Code:      imap.CodeCapability,
+		Arguments: imap.FormatStringList(conn.Capabilities()),
 	})
 }
 
+func canAuth(conn Conn) bool {
+	for _, cap := range conn.Capabilities() {
+		if cap == "AUTH=PLAIN" {
+			return true
+		}
+	}
+	return false
+}
+
 type Login struct {
 	commands.Login
 }
 
-func (cmd *Login) Handle(conn *Conn) error {
-	if conn.State != common.NotAuthenticatedState {
+func (cmd *Login) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.State != imap.NotAuthenticatedState {
 		return ErrAlreadyAuthenticated
 	}
-	if !conn.CanAuth() {
+	if !canAuth(conn) {
 		return ErrAuthDisabled
 	}
 
-	user, err := conn.Server.Backend.Login(cmd.Username, cmd.Password)
+	user, err := conn.Server().Backend.Login(cmd.Username, cmd.Password)
 	if err != nil {
 		return err
 	}
 
-	conn.State = common.AuthenticatedState
-	conn.User = user
+	ctx.State = imap.AuthenticatedState
+	ctx.User = user
 	return afterAuthStatus(conn)
 }
 
@@ -92,20 +104,21 @@ type Authenticate struct {
 	commands.Authenticate
 }
 
-func (cmd *Authenticate) Handle(conn *Conn) error {
-	if conn.State != common.NotAuthenticatedState {
+func (cmd *Authenticate) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.State != imap.NotAuthenticatedState {
 		return ErrAlreadyAuthenticated
 	}
-	if !conn.CanAuth() {
+	if !canAuth(conn) {
 		return ErrAuthDisabled
 	}
 
 	mechanisms := map[string]sasl.Server{}
-	for name, newSasl := range conn.Server.auths {
+	for name, newSasl := range conn.Server().auths {
 		mechanisms[name] = newSasl(conn)
 	}
 
-	err := cmd.Authenticate.Handle(mechanisms, conn.Reader, conn.Writer)
+	err := cmd.Authenticate.Handle(mechanisms, conn, conn.writer())
 	if err != nil {
 		return err
 	}
diff --git a/server/cmd_selected.go b/server/cmd_selected.go
index 453534a..744bb06 100644
--- a/server/cmd_selected.go
+++ b/server/cmd_selected.go
@@ -4,15 +4,15 @@ import (
 	"errors"
 	"strings"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/commands"
 	"github.com/emersion/go-imap/responses"
 )
 
-// Common errors in Selected state.
+// imap errors in Selected state.
 var (
 	ErrNoMailboxSelected = errors.New("No mailbox selected")
-	ErrMailboxReadOnly = errors.New("Mailbox opened in read-only mode")
+	ErrMailboxReadOnly   = errors.New("Mailbox opened in read-only mode")
 )
 
 // A command handler that supports UIDs.
@@ -20,36 +20,38 @@ type UidHandler interface {
 	Handler
 
 	// Handle this command using UIDs for a given connection.
-	UidHandle(conn *Conn) error
+	UidHandle(conn Conn) error
 }
 
 type Check struct {
 	commands.Check
 }
 
-func (cmd *Check) Handle(conn *Conn) error {
-	if conn.Mailbox == nil {
+func (cmd *Check) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
-	if conn.MailboxReadOnly {
+	if ctx.MailboxReadOnly {
 		return ErrMailboxReadOnly
 	}
 
-	return conn.Mailbox.Check()
+	return ctx.Mailbox.Check()
 }
 
 type Close struct {
 	commands.Close
 }
 
-func (cmd *Close) Handle(conn *Conn) error {
-	if conn.Mailbox == nil {
+func (cmd *Close) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
 
-	mailbox := conn.Mailbox
-	conn.Mailbox = nil
-	conn.MailboxReadOnly = false
+	mailbox := ctx.Mailbox
+	ctx.Mailbox = nil
+	ctx.MailboxReadOnly = false
 
 	if err := mailbox.Expunge(); err != nil {
 		return err
@@ -63,38 +65,39 @@ type Expunge struct {
 	commands.Expunge
 }
 
-func (cmd *Expunge) Handle(conn *Conn) error {
-	if conn.Mailbox == nil {
+func (cmd *Expunge) Handle(conn Conn) error {
+	ctx := conn.Context()
+	if ctx.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
-	if conn.MailboxReadOnly {
+	if ctx.MailboxReadOnly {
 		return ErrMailboxReadOnly
 	}
 
 	// Get a list of messages that will be deleted
 	// That will allow us to send expunge updates if the backend doesn't support it
 	var seqnums []uint32
-	if conn.Server.Updates == nil {
+	if conn.Server().Updates == nil {
 		var err error
-		seqnums, err = conn.Mailbox.SearchMessages(false, &common.SearchCriteria{Deleted: true})
+		seqnums, err = ctx.Mailbox.SearchMessages(false, &imap.SearchCriteria{Deleted: true})
 		if err != nil {
 			return err
 		}
 	}
 
-	if err := conn.Mailbox.Expunge(); err != nil {
+	if err := ctx.Mailbox.Expunge(); err != nil {
 		return err
 	}
 
 	// If the backend doesn't support expunge updates, let's do it ourselves
-	if conn.Server.Updates == nil {
+	if conn.Server().Updates == nil {
 		done := make(chan error)
 		defer close(done)
 
 		ch := make(chan uint32)
 		res := &responses.Expunge{SeqNums: ch}
 
-		go (func () {
+		go (func() {
 			done <- conn.WriteResp(res)
 		})()
 
@@ -117,12 +120,13 @@ type Search struct {
 	commands.Search
 }
 
-func (cmd *Search) handle(uid bool, conn *Conn) error {
-	if conn.Mailbox == nil {
+func (cmd *Search) handle(uid bool, conn Conn) error {
+	ctx := conn.Context()
+	if ctx.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
 
-	ids, err := conn.Mailbox.SearchMessages(uid, cmd.Criteria)
+	ids, err := ctx.Mailbox.SearchMessages(uid, cmd.Criteria)
 	if err != nil {
 		return err
 	}
@@ -131,11 +135,11 @@ func (cmd *Search) handle(uid bool, conn *Conn) error {
 	return conn.WriteResp(res)
 }
 
-func (cmd *Search) Handle(conn *Conn) error {
+func (cmd *Search) Handle(conn Conn) error {
 	return cmd.handle(false, conn)
 }
 
-func (cmd *Search) UidHandle(conn *Conn) error {
+func (cmd *Search) UidHandle(conn Conn) error {
 	return cmd.handle(true, conn)
 }
 
@@ -143,22 +147,22 @@ type Fetch struct {
 	commands.Fetch
 }
 
-func (cmd *Fetch) handle(uid bool, conn *Conn) error {
-	if conn.Mailbox == nil {
+func (cmd *Fetch) handle(uid bool, conn Conn) error {
+	ctx := conn.Context()
+	if ctx.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
 
-	ch := make(chan *common.Message)
+	ch := make(chan *imap.Message)
 	res := &responses.Fetch{Messages: ch}
 
-	// TODO: if ListMessages() fails, make sure the goroutine is stopped
-	done := make(chan error)
-	go (func () {
+	done := make(chan error, 1)
+	go (func() {
 		done <- conn.WriteResp(res)
 		close(done)
 	})()
 
-	err := conn.Mailbox.ListMessages(uid, cmd.SeqSet, cmd.Items, ch)
+	err := ctx.Mailbox.ListMessages(uid, cmd.SeqSet, cmd.Items, ch)
 	if err != nil {
 		return err
 	}
@@ -166,11 +170,11 @@ func (cmd *Fetch) handle(uid bool, conn *Conn) error {
 	return <-done
 }
 
-func (cmd *Fetch) Handle(conn *Conn) error {
+func (cmd *Fetch) Handle(conn Conn) error {
 	return cmd.handle(false, conn)
 }
 
-func (cmd *Fetch) UidHandle(conn *Conn) error {
+func (cmd *Fetch) UidHandle(conn Conn) error {
 	// Append UID to the list of requested items if it isn't already present
 	hasUid := false
 	for _, item := range cmd.Items {
@@ -190,22 +194,23 @@ type Store struct {
 	commands.Store
 }
 
-func (cmd *Store) handle(uid bool, conn *Conn) error {
-	if conn.Mailbox == nil {
+func (cmd *Store) handle(uid bool, conn Conn) error {
+	ctx := conn.Context()
+	if ctx.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
-	if conn.MailboxReadOnly {
+	if ctx.MailboxReadOnly {
 		return ErrMailboxReadOnly
 	}
 
 	itemStr := cmd.Item
-	silent := strings.HasSuffix(itemStr, common.SilentOp)
+	silent := strings.HasSuffix(itemStr, imap.SilentOp)
 	if silent {
-		itemStr = strings.TrimSuffix(itemStr, common.SilentOp)
+		itemStr = strings.TrimSuffix(itemStr, imap.SilentOp)
 	}
-	item := common.FlagsOp(itemStr)
+	item := imap.FlagsOp(itemStr)
 
-	if item != common.SetFlags && item != common.AddFlags && item != common.RemoveFlags {
+	if item != imap.SetFlags && item != imap.AddFlags && item != imap.RemoveFlags {
 		return errors.New("Unsupported STORE operation")
 	}
 
@@ -213,23 +218,24 @@ func (cmd *Store) handle(uid bool, conn *Conn) error {
 	if !ok {
 		return errors.New("Flags must be a list")
 	}
-	flags, err := common.ParseStringList(flagsList)
+	flags, err := imap.ParseStringList(flagsList)
 	if err != nil {
 		return err
 	}
 
 	// If the backend supports message updates, this will prevent this connection
 	// from receiving them
-	conn.silent = silent
-	err = conn.Mailbox.UpdateMessagesFlags(uid, cmd.SeqSet, item, flags)
-	conn.silent = false
+	// TODO: find a better way to do this, without conn.silent
+	*conn.silent() = silent
+	err = ctx.Mailbox.UpdateMessagesFlags(uid, cmd.SeqSet, item, flags)
+	*conn.silent() = false
 	if err != nil {
 		return err
 	}
 
 	// Not silent: send FETCH updates if the backend doesn't support message
 	// updates
-	if conn.Server.Updates == nil && !silent {
+	if conn.Server().Updates == nil && !silent {
 		inner := &Fetch{}
 		inner.SeqSet = cmd.SeqSet
 		inner.Items = []string{"FLAGS"}
@@ -245,11 +251,11 @@ func (cmd *Store) handle(uid bool, conn *Conn) error {
 	return nil
 }
 
-func (cmd *Store) Handle(conn *Conn) error {
+func (cmd *Store) Handle(conn Conn) error {
 	return cmd.handle(false, conn)
 }
 
-func (cmd *Store) UidHandle(conn *Conn) error {
+func (cmd *Store) UidHandle(conn Conn) error {
 	return cmd.handle(true, conn)
 }
 
@@ -257,19 +263,20 @@ type Copy struct {
 	commands.Copy
 }
 
-func (cmd *Copy) handle(uid bool, conn *Conn) error {
-	if conn.Mailbox == nil {
+func (cmd *Copy) handle(uid bool, conn Conn) error {
+	ctx := conn.Context()
+	if ctx.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
 
-	return conn.Mailbox.CopyMessages(uid, cmd.SeqSet, cmd.Mailbox)
+	return ctx.Mailbox.CopyMessages(uid, cmd.SeqSet, cmd.Mailbox)
 }
 
-func (cmd *Copy) Handle(conn *Conn) error {
+func (cmd *Copy) Handle(conn Conn) error {
 	return cmd.handle(false, conn)
 }
 
-func (cmd *Copy) UidHandle(conn *Conn) error {
+func (cmd *Copy) UidHandle(conn Conn) error {
 	return cmd.handle(true, conn)
 }
 
@@ -277,9 +284,9 @@ type Uid struct {
 	commands.Uid
 }
 
-func (cmd *Uid) Handle(conn *Conn) error {
+func (cmd *Uid) Handle(conn Conn) error {
 	inner := cmd.Cmd.Command()
-	hdlr, err := conn.Server.getCommandHandler(inner)
+	hdlr, err := conn.Server().commandHandler(inner)
 	if err != nil {
 		return err
 	}
@@ -293,8 +300,8 @@ func (cmd *Uid) Handle(conn *Conn) error {
 		return err
 	}
 
-	return ErrStatusResp(&common.StatusResp{
-		Type: common.StatusOk,
-		Info: common.Uid + " " + inner.Name + " completed",
+	return ErrStatusResp(&imap.StatusResp{
+		Type: imap.StatusOk,
+		Info: imap.Uid + " " + inner.Name + " completed",
 	})
 }
diff --git a/server/conn.go b/server/conn.go
index 15026b2..42aa45d 100644
--- a/server/conn.go
+++ b/server/conn.go
@@ -2,26 +2,50 @@ package server
 
 import (
 	"crypto/tls"
+	"io"
 	"log"
 	"net"
 	"sync"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/backend"
 )
 
-type Conn struct {
-	*common.Conn
-
-	isTLS bool
-	continues chan bool
-	silent bool
-	locker sync.Locker
+// A connection.
+type Conn interface {
+	io.Reader
+
+	// Get this connection's server.
+	Server() *Server
+	// Get this connection's context.
+	Context() *Context
+	// Get a list of capabilities enabled for this connection.
+	Capabilities() []string
+	// Write a response to this connection.
+	WriteResp(res imap.WriterTo) error
+	// IsTLS returns true if TLS is enabled.
+	IsTLS() bool
+	// TLSState returns the TLS connection state if TLS is enabled, nil otherwise.
+	TLSState() *tls.ConnectionState
+	// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
+	// tunnel.
+	Upgrade(upgrader imap.ConnUpgrader) error
+	// Close this connection.
+	Close() error
+
+	conn() *imap.Conn
+	reader() *imap.Reader
+	writer() imap.Writer
+	locker() sync.Locker
+	greet() error
+	setTLSConn(*tls.Conn)
+	silent() *bool // TODO: remove this
+}
 
-	// This connection's server.
-	Server *Server
+// A connection's context.
+type Context struct {
 	// This connection's current state.
-	State common.ConnState
+	State imap.ConnState
 	// If the client is logged in, the user.
 	User backend.User
 	// If the client has selected a mailbox, the mailbox.
@@ -30,10 +54,69 @@ type Conn struct {
 	MailboxReadOnly bool
 }
 
+type conn struct {
+	*imap.Conn
+
+	s         *Server
+	ctx       *Context
+	l         sync.Locker
+	tlsConn   *tls.Conn
+	continues chan bool
+	silentVal bool
+}
+
+func newConn(s *Server, c net.Conn) *conn {
+	continues := make(chan bool)
+	r := imap.NewServerReader(nil, continues)
+	w := imap.NewWriter(nil)
+
+	tlsConn, _ := c.(*tls.Conn)
+
+	conn := &conn{
+		Conn: imap.NewConn(c, r, w),
+
+		s: s,
+		l: &sync.Mutex{},
+		ctx: &Context{
+			State: imap.NotAuthenticatedState,
+		},
+		tlsConn:   tlsConn,
+		continues: continues,
+	}
+
+	go conn.sendContinuationReqs()
+
+	return conn
+}
+
+func (c *conn) conn() *imap.Conn {
+	return c.Conn
+}
+
+func (c *conn) reader() *imap.Reader {
+	return c.Reader
+}
+
+func (c *conn) writer() imap.Writer {
+	return c.Writer
+}
+
+func (c *conn) locker() sync.Locker {
+	return c.l
+}
+
+func (c *conn) Server() *Server {
+	return c.s
+}
+
+func (c *conn) Context() *Context {
+	return c.ctx
+}
+
 // Write a response to this connection.
-func (c *Conn) WriteResp(res common.WriterTo) error {
-	c.locker.Lock()
-	defer c.locker.Unlock()
+func (c *conn) WriteResp(res imap.WriterTo) error {
+	c.l.Lock()
+	defer c.l.Unlock()
 
 	if err := res.WriteTo(c.Writer); err != nil {
 		return err
@@ -43,9 +126,9 @@ func (c *Conn) WriteResp(res common.WriterTo) error {
 }
 
 // Close this connection.
-func (c *Conn) Close() error {
-	if c.User != nil {
-		c.User.Logout()
+func (c *conn) Close() error {
+	if c.ctx.User != nil {
+		c.ctx.User.Logout()
 	}
 
 	if err := c.Conn.Close(); err != nil {
@@ -54,84 +137,77 @@ func (c *Conn) Close() error {
 
 	close(c.continues)
 
-	c.State = common.LogoutState
+	c.ctx.State = imap.LogoutState
 	return nil
 }
 
-func (c *Conn) getCaps() (caps []string) {
-	caps = []string{"IMAP4rev1"}
+func (c *conn) Capabilities() (caps []string) {
+	caps = c.s.Capabilities(c.ctx.State)
 
-	if c.State == common.NotAuthenticatedState {
-		if !c.IsTLS() && c.Server.TLSConfig != nil {
+	if c.ctx.State == imap.NotAuthenticatedState {
+		if !c.IsTLS() && c.s.TLSConfig != nil {
 			caps = append(caps, "STARTTLS")
 		}
 
-		if !c.CanAuth() {
+		if !c.canAuth() {
 			caps = append(caps, "LOGINDISABLED")
 		} else {
-			caps = append(caps, "AUTH=PLAIN")
+			for name, _ := range c.s.auths {
+				caps = append(caps, "AUTH="+name)
+			}
 		}
 	}
 
-	caps = append(caps, c.Server.getCaps(c.State)...)
 	return
 }
 
-func (c *Conn) sendContinuationReqs() {
+func (c *conn) sendContinuationReqs() {
 	for range c.continues {
-		cont := &common.ContinuationResp{Info: "send literal"}
+		cont := &imap.ContinuationResp{Info: "send literal"}
 		if err := c.WriteResp(cont); err != nil {
 			log.Println("WARN: cannot send continuation request:", err)
 		}
 	}
 }
 
-func (c *Conn) greet() error {
-	caps := c.getCaps()
+func (c *conn) greet() error {
+	caps := c.Capabilities()
 	args := make([]interface{}, len(caps))
 	for i, cap := range caps {
 		args[i] = cap
 	}
 
-	greeting := &common.StatusResp{
-		Type: common.StatusOk,
-		Code: common.CodeCapability,
+	greeting := &imap.StatusResp{
+		Type:      imap.StatusOk,
+		Code:      imap.CodeCapability,
 		Arguments: args,
-		Info: "IMAP4rev1 Service Ready",
+		Info:      "IMAP4rev1 Service Ready",
 	}
 
 	return c.WriteResp(greeting)
 }
 
-// Check if this connection is encrypted.
-func (c *Conn) IsTLS() bool {
-	return c.isTLS
+func (c *conn) setTLSConn(tlsConn *tls.Conn) {
+	c.tlsConn = tlsConn
 }
 
-// Check if the client can use plain text authentication.
-func (c *Conn) CanAuth() bool {
-	return c.IsTLS() || c.Server.AllowInsecureAuth
+func (c *conn) IsTLS() bool {
+	return c.tlsConn != nil
 }
 
-func newConn(s *Server, c net.Conn) *Conn {
-	continues := make(chan bool)
-	r := common.NewServerReader(nil, continues)
-	w := common.NewWriter(nil)
-
-	_, isTLS := c.(*tls.Conn)
-
-	conn := &Conn{
-		Conn: common.NewConn(c, r, w),
-
-		isTLS: isTLS,
-		continues: continues,
-		locker: &sync.Mutex{},
-
-		Server: s,
-		State: common.NotAuthenticatedState,
+func (c *conn) TLSState() *tls.ConnectionState {
+	if c.tlsConn != nil {
+		state := c.tlsConn.ConnectionState()
+		return &state
 	}
+	return nil
+}
 
-	go conn.sendContinuationReqs()
+// Check if the client can use plain text authentication.
+func (c *conn) canAuth() bool {
+	return c.IsTLS() || c.s.AllowInsecureAuth
+}
 
-	return conn
+func (c *conn) silent() *bool {
+	return &c.silentVal
 }
diff --git a/server/server.go b/server/server.go
index 86ee07f..5aac3c3 100644
--- a/server/server.go
+++ b/server/server.go
@@ -9,7 +9,7 @@ import (
 	"log"
 	"net"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/backend"
 	"github.com/emersion/go-imap/responses"
 	"github.com/emersion/go-sasl"
@@ -17,13 +17,13 @@ import (
 
 // A command handler.
 type Handler interface {
-	common.Parser
+	imap.Parser
 
 	// Handle this command for a given connection.
 	//
 	// By default, after this function has returned a status response is sent. To
 	// prevent this behavior handlers can use ErrStatusResp or ErrNoStatusResp.
-	Handle(conn *Conn) error
+	Handle(conn Conn) error
 }
 
 // A connection upgrader. If a Handler is also an Upgrader, the connection will
@@ -33,17 +33,35 @@ type Handler interface {
 // COMPRESS).
 type Upgrader interface {
 	// Upgrade the connection. This method should call conn.Upgrade().
-	Upgrade(conn *Conn) error
+	Upgrade(conn Conn) error
 }
 
 // A function that creates handlers.
 type HandlerFactory func() Handler
 
 // A function that creates SASL servers.
-type SaslServerFactory func(conn *Conn) sasl.Server
+type SaslServerFactory func(conn Conn) sasl.Server
+
+// An IMAP extension.
+type Extension interface {
+	// Get capabilities provided by this extension for a given connection state.
+	Capabilities(state imap.ConnState) []string
+	// Get the command handler factory for the provided command name.
+	Command(name string) HandlerFactory
+}
+
+// An extension that provides additional features to each connection.
+type ConnExtension interface {
+	Extension
+
+	// This function will be called when a client connects to the server. It can
+	// be used to add new features to the default Conn interface by implementing
+	// new methods.
+	NewConn(c Conn) Conn
+}
 
 type errStatusResp struct {
-	resp *common.StatusResp
+	resp *imap.StatusResp
 }
 
 func (err *errStatusResp) Error() string {
@@ -54,7 +72,7 @@ func (err *errStatusResp) Error() string {
 // response. The response tag must be empty.
 //
 // To disable the default status response, use ErrNoStatusResp instead.
-func ErrStatusResp(res *common.StatusResp) error {
+func ErrStatusResp(res *imap.StatusResp) error {
 	return &errStatusResp{res}
 }
 
@@ -67,11 +85,11 @@ func ErrNoStatusResp() error {
 // An IMAP server.
 type Server struct {
 	listener net.Listener
-	conns []*Conn
+	conns    []Conn
 
-	caps map[string]common.ConnState
-	commands map[string]HandlerFactory
-	auths map[string]SaslServerFactory
+	commands   map[string]HandlerFactory
+	auths      map[string]SaslServerFactory
+	extensions []Extension
 
 	// TCP address to listen on.
 	Addr string
@@ -90,12 +108,11 @@ type Server struct {
 // Create a new IMAP server from an existing listener.
 func New(bkd backend.Backend) *Server {
 	s := &Server{
-		caps: map[string]common.ConnState{},
 		Backend: bkd,
 	}
 
 	s.auths = map[string]SaslServerFactory{
-		"PLAIN": func(conn *Conn) sasl.Server {
+		"PLAIN": func(conn Conn) sasl.Server {
 			return sasl.NewPlainServer(func(identity, username, password string) error {
 				if identity != "" && identity != username {
 					return errors.New("Identities not supported")
@@ -106,50 +123,51 @@ func New(bkd backend.Backend) *Server {
 					return err
 				}
 
-				conn.State = common.AuthenticatedState
-				conn.User = user
+				ctx := conn.Context()
+				ctx.State = imap.AuthenticatedState
+				ctx.User = user
 				return nil
 			})
 		},
 	}
 
 	s.commands = map[string]HandlerFactory{
-		common.Noop: func() Handler { return &Noop{} },
-		common.Capability: func() Handler { return &Capability{} },
-		common.Logout: func() Handler { return &Logout{} },
+		imap.Noop:       func() Handler { return &Noop{} },
+		imap.Capability: func() Handler { return &Capability{} },
+		imap.Logout:     func() Handler { return &Logout{} },
 
-		common.StartTLS: func() Handler { return &StartTLS{} },
-		common.Login: func() Handler { return &Login{} },
-		common.Authenticate: func() Handler { return &Authenticate{} },
+		imap.StartTLS:     func() Handler { return &StartTLS{} },
+		imap.Login:        func() Handler { return &Login{} },
+		imap.Authenticate: func() Handler { return &Authenticate{} },
 
-		common.Select: func() Handler { return &Select{} },
-		common.Examine: func() Handler {
+		imap.Select: func() Handler { return &Select{} },
+		imap.Examine: func() Handler {
 			hdlr := &Select{}
 			hdlr.ReadOnly = true
 			return hdlr
 		},
-		common.Create: func() Handler { return &Create{} },
-		common.Delete: func() Handler { return &Delete{} },
-		common.Rename: func() Handler { return &Rename{} },
-		common.Subscribe: func() Handler { return &Subscribe{} },
-		common.Unsubscribe: func() Handler { return &Unsubscribe{} },
-		common.List: func() Handler { return &List{} },
-		common.Lsub: func() Handler {
+		imap.Create:      func() Handler { return &Create{} },
+		imap.Delete:      func() Handler { return &Delete{} },
+		imap.Rename:      func() Handler { return &Rename{} },
+		imap.Subscribe:   func() Handler { return &Subscribe{} },
+		imap.Unsubscribe: func() Handler { return &Unsubscribe{} },
+		imap.List:        func() Handler { return &List{} },
+		imap.Lsub: func() Handler {
 			hdlr := &List{}
 			hdlr.Subscribed = true
 			return hdlr
 		},
-		common.Status: func() Handler { return &Status{} },
-		common.Append: func() Handler { return &Append{} },
-
-		common.Check: func() Handler { return &Check{} },
-		common.Close: func() Handler { return &Close{} },
-		common.Expunge: func() Handler { return &Expunge{} },
-		common.Search: func() Handler { return &Search{} },
-		common.Fetch: func() Handler { return &Fetch{} },
-		common.Store: func() Handler { return &Store{} },
-		common.Copy: func() Handler { return &Copy{} },
-		common.Uid: func() Handler { return &Uid{} },
+		imap.Status: func() Handler { return &Status{} },
+		imap.Append: func() Handler { return &Append{} },
+
+		imap.Check:   func() Handler { return &Check{} },
+		imap.Close:   func() Handler { return &Close{} },
+		imap.Expunge: func() Handler { return &Expunge{} },
+		imap.Search:  func() Handler { return &Search{} },
+		imap.Fetch:   func() Handler { return &Fetch{} },
+		imap.Store:   func() Handler { return &Store{} },
+		imap.Copy:    func() Handler { return &Copy{} },
+		imap.Uid:     func() Handler { return &Uid{} },
 	}
 
 	return s
@@ -168,9 +186,15 @@ func (s *Server) Serve(l net.Listener) error {
 			return err
 		}
 
-		conn := newConn(s, c)
+		var conn Conn = newConn(s, c)
 		if s.Debug {
-			conn.SetDebug(true)
+			conn.conn().SetDebug(true)
+		}
+
+		for _, ext := range s.extensions {
+			if ext, ok := ext.(ConnExtension); ok {
+				conn = ext.NewConn(conn)
+			}
 		}
 
 		go s.handleConn(conn)
@@ -213,7 +237,7 @@ func (s *Server) ListenAndServeTLS() error {
 	return s.Serve(l)
 }
 
-func (s *Server) handleConn(conn *Conn) error {
+func (s *Server) handleConn(conn Conn) error {
 	s.conns = append(s.conns, conn)
 	defer (func() {
 		conn.Close()
@@ -232,22 +256,22 @@ func (s *Server) handleConn(conn *Conn) error {
 	}
 
 	for {
-		if conn.State == common.LogoutState {
+		if conn.Context().State == imap.LogoutState {
 			return nil
 		}
 
-		var res *common.StatusResp
+		var res *imap.StatusResp
 		var up Upgrader
 
-		conn.Wait()
-		fields, err := conn.ReadLine()
-		if err == io.EOF || conn.State == common.LogoutState {
+		conn.conn().Wait()
+		fields, err := conn.conn().ReadLine()
+		if err == io.EOF || conn.Context().State == imap.LogoutState {
 			return nil
 		}
 		if err != nil {
-			if common.IsParseError(err) {
-				res = &common.StatusResp{
-					Type: common.StatusBad,
+			if imap.IsParseError(err) {
+				res = &imap.StatusResp{
+					Type: imap.StatusBad,
 					Info: err.Error(),
 				}
 			} else {
@@ -255,20 +279,20 @@ func (s *Server) handleConn(conn *Conn) error {
 				return err
 			}
 		} else {
-			cmd := &common.Command{}
+			cmd := &imap.Command{}
 			if err := cmd.Parse(fields); err != nil {
-				res = &common.StatusResp{
-					Tag: cmd.Tag,
-					Type: common.StatusBad,
+				res = &imap.StatusResp{
+					Tag:  cmd.Tag,
+					Type: imap.StatusBad,
 					Info: err.Error(),
 				}
 			} else {
 				var err error
 				res, up, err = s.handleCommand(cmd, conn)
 				if err != nil {
-					res = &common.StatusResp{
-						Tag: cmd.Tag,
-						Type: common.StatusBad,
+					res = &imap.StatusResp{
+						Tag:  cmd.Tag,
+						Type: imap.StatusBad,
 						Info: err.Error(),
 					}
 				}
@@ -281,7 +305,7 @@ func (s *Server) handleConn(conn *Conn) error {
 				continue
 			}
 
-			if up != nil && res.Type == common.StatusOk {
+			if up != nil && res.Type == imap.StatusOk {
 				if err := up.Upgrade(conn); err != nil {
 					log.Println("Error upgrading connection:", err)
 					return err
@@ -291,9 +315,21 @@ func (s *Server) handleConn(conn *Conn) error {
 	}
 }
 
-func (s *Server) getCommandHandler(cmd *common.Command) (hdlr Handler, err error) {
-	newHandler, ok := s.commands[cmd.Name]
-	if !ok {
+// Get a command handler factory for the provided command name.
+func (s *Server) Command(name string) HandlerFactory {
+	// Extensions can override builtin commands
+	for _, ext := range s.extensions {
+		if h := ext.Command(name); h != nil {
+			return h
+		}
+	}
+
+	return s.commands[name]
+}
+
+func (s *Server) commandHandler(cmd *imap.Command) (hdlr Handler, err error) {
+	newHandler := s.Command(cmd.Name)
+	if newHandler == nil {
 		err = errors.New("Unknown command")
 		return
 	}
@@ -303,8 +339,8 @@ func (s *Server) getCommandHandler(cmd *common.Command) (hdlr Handler, err error
 	return
 }
 
-func (s *Server) handleCommand(cmd *common.Command, conn *Conn) (res *common.StatusResp, up Upgrader, err error) {
-	hdlr, err := s.getCommandHandler(cmd)
+func (s *Server) handleCommand(cmd *imap.Command, conn Conn) (res *imap.StatusResp, up Upgrader, err error) {
+	hdlr, err := s.commandHandler(cmd)
 	if err != nil {
 		return
 	}
@@ -313,20 +349,20 @@ func (s *Server) handleCommand(cmd *common.Command, conn *Conn) (res *common.Sta
 	if statusErr, ok := hdlrErr.(*errStatusResp); ok {
 		res = statusErr.resp
 	} else if hdlrErr != nil {
-		res = &common.StatusResp{
-			Type: common.StatusNo,
+		res = &imap.StatusResp{
+			Type: imap.StatusNo,
 			Info: hdlrErr.Error(),
 		}
 	} else {
-		res = &common.StatusResp{
-			Type: common.StatusOk,
+		res = &imap.StatusResp{
+			Type: imap.StatusOk,
 		}
 	}
 
 	if res != nil {
 		res.Tag = cmd.Tag
 
-		if res.Type == common.StatusOk && res.Info == "" {
+		if res.Type == imap.StatusOk && res.Info == "" {
 			res.Info = cmd.Name + " completed"
 		}
 	}
@@ -343,7 +379,7 @@ func (s *Server) listenUpdates() (err error) {
 	s.Updates = updater.Updates()
 
 	var update *backend.Update
-	var res common.WriterTo
+	var res imap.WriterTo
 	for {
 		// TODO: do not generate response if nobody will receive it
 
@@ -357,7 +393,7 @@ func (s *Server) listenUpdates() (err error) {
 		case message := <-s.Updates.Messages:
 			update = &message.Update
 
-			ch := make(chan *common.Message, 1)
+			ch := make(chan *imap.Message, 1)
 			ch <- message.Message
 			close(ch)
 
@@ -374,31 +410,33 @@ func (s *Server) listenUpdates() (err error) {
 
 		// Format response
 		b := &bytes.Buffer{}
-		w := common.NewWriter(b)
+		w := imap.NewWriter(b)
 		if err := res.WriteTo(w); err != nil {
 			log.Println("WARN: cannot format unlateral update:", err)
 		}
 
 		for _, conn := range s.conns {
-			if update.Username != "" && (conn.User == nil || conn.User.Username() != update.Username) {
+			ctx := conn.Context()
+
+			if update.Username != "" && (ctx.User == nil || ctx.User.Username() != update.Username) {
 				continue
 			}
-			if update.Mailbox != "" && (conn.Mailbox == nil || conn.Mailbox.Name() != update.Mailbox) {
+			if update.Mailbox != "" && (ctx.Mailbox == nil || ctx.Mailbox.Name() != update.Mailbox) {
 				continue
 			}
-			if conn.silent {
+			if *conn.silent() {
 				// If silent is set, do not send message updates
 				if _, ok := res.(*responses.Fetch); ok {
 					continue
 				}
 			}
 
-			conn.locker.Lock()
-			if _, err := conn.Writer.Write(b.Bytes()); err != nil {
+			conn.locker().Lock()
+			if _, err := conn.writer().Write(b.Bytes()); err != nil {
 				log.Println("WARN: error sending unilateral update:", err)
 			}
-			conn.Flush()
-			conn.locker.Unlock()
+			conn.conn().Flush()
+			conn.locker().Unlock()
 		}
 
 		if update.Done != nil {
@@ -407,11 +445,17 @@ func (s *Server) listenUpdates() (err error) {
 	}
 }
 
-func (s *Server) getCaps(currentState common.ConnState) (caps []string) {
-	for name, state := range s.caps {
-		if currentState & state != 0 {
-			caps = append(caps, name)
-		}
+// Get this server's capabilities for the provided connection state.
+func (s *Server) Capabilities(state imap.ConnState) (caps []string) {
+	caps = []string{"IMAP4rev1"}
+
+	if state == imap.AuthenticatedState {
+		// Also add capabilities for imap.SelectedState
+		state |= imap.SelectedState
+	}
+
+	for _, ext := range s.extensions {
+		caps = append(caps, ext.Capabilities(state)...)
 	}
 	return
 }
@@ -429,26 +473,18 @@ func (s *Server) Close() error {
 	return nil
 }
 
-// Register a new capability that will be advertised by this server.
+// Enable some IMAP extensions on this server.
 //
 // This function should not be called directly, it must only be used by
 // libraries implementing extensions of the IMAP protocol.
-func (s *Server) RegisterCapability(name string, state common.ConnState) {
-	s.caps[name] = state
+func (s *Server) Enable(extensions ...Extension) {
+	s.extensions = append(s.extensions, extensions...)
 }
 
-// Register a new authentication mechanism for this server.
+// Enable an authentication mechanism on this server.
 //
 // This function should not be called directly, it must only be used by
 // libraries implementing extensions of the IMAP protocol.
-func (s *Server) RegisterAuth(name string, f SaslServerFactory) {
+func (s *Server) EnableAuth(name string, f SaslServerFactory) {
 	s.auths[name] = f
 }
-
-// Register a new command for this server.
-//
-// This function should not be called directly, it must only be used by
-// libraries implementing extensions of the IMAP protocol.
-func (s *Server) RegisterCommand(name string, f HandlerFactory) {
-	s.commands[name] = f
-}
diff --git a/common/status.go b/status.go
similarity index 73%
rename from common/status.go
rename to status.go
index 340c0af..5a5aeb2 100644
--- a/common/status.go
+++ b/status.go
@@ -1,4 +1,4 @@
-package common
+package imap
 
 import (
 	"errors"
@@ -41,17 +41,17 @@ const (
 
 // Status response codes defined in RFC 3501 section 7.1.
 const (
-	CodeAlert = "ALERT"
-	CodeBadCharset = "BADCHARSET"
-	CodeCapability = "CAPABILITY"
-	CodeParse = "PARSE"
+	CodeAlert          = "ALERT"
+	CodeBadCharset     = "BADCHARSET"
+	CodeCapability     = "CAPABILITY"
+	CodeParse          = "PARSE"
 	CodePermanentFlags = "PERMANENTFLAGS"
-	CodeReadOnly = "READ-ONLY"
-	CodeReadWrite = "READ-WRITE"
-	CodeTryCreate = "TRYCREATE"
-	CodeUidNext = "UIDNEXT"
-	CodeUidValidity = "UIDVALIDITY"
-	CodeUnseen = "UNSEEN"
+	CodeReadOnly       = "READ-ONLY"
+	CodeReadWrite      = "READ-WRITE"
+	CodeTryCreate      = "TRYCREATE"
+	CodeUidNext        = "UIDNEXT"
+	CodeUidValidity    = "UIDVALIDITY"
+	CodeUnseen         = "UNSEEN"
 )
 
 // A status response.
@@ -79,7 +79,7 @@ type StatusResp struct {
 func (r *StatusResp) Err() error {
 	if r == nil {
 		// No status response, connection closed before we get one
-		return errors.New("Connection closed during command execution")
+		return errors.New("imap: connection closed during command execution")
 	}
 
 	if r.Type == StatusNo || r.Type == StatusBad {
@@ -88,30 +88,35 @@ func (r *StatusResp) Err() error {
 	return nil
 }
 
-func (r *StatusResp) WriteTo(w *Writer) (err error) {
+func (r *StatusResp) WriteTo(w Writer) error {
+	ww := w.writer()
+
 	tag := r.Tag
 	if tag == "" {
 		tag = "*"
 	}
 
-	if _, err = w.WriteFields([]interface{}{tag, string(r.Type)}); err != nil {
-		return
+	if err := ww.writeFields([]interface{}{tag, string(r.Type)}); err != nil {
+		return err
 	}
 
-	if _, err = w.WriteSp(); err != nil {
-		return
+	if err := ww.writeString(string(sp)); err != nil {
+		return err
 	}
 
 	if r.Code != "" {
-		if _, err = w.WriteRespCode(r.Code, r.Arguments); err != nil {
-			return
+		if err := ww.writeRespCode(r.Code, r.Arguments); err != nil {
+			return err
 		}
 
-		if _, err = w.WriteSp(); err != nil {
-			return
+		if err := ww.writeString(string(sp)); err != nil {
+			return err
 		}
 	}
 
-	_, err = w.WriteInfo(r.Info)
-	return
+	if err := ww.writeString(r.Info); err != nil {
+		return err
+	}
+
+	return ww.writeCrlf()
 }
diff --git a/utf7/decoder.go b/utf7/decoder.go
index d2d65eb..0989212 100644
--- a/utf7/decoder.go
+++ b/utf7/decoder.go
@@ -29,7 +29,7 @@ func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err er
 		}
 
 		if ch != '&' {
-			if nDst + 1 > len(dst) {
+			if nDst+1 > len(dst) {
 				err = transform.ErrShortDst
 				return
 			}
@@ -80,7 +80,7 @@ func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err er
 			return
 		}
 
-		if nDst + len(b) > len(dst) {
+		if nDst+len(b) > len(dst) {
 			err = transform.ErrShortDst
 			return
 		}
diff --git a/utf7/encoder.go b/utf7/encoder.go
index d7a60fd..da20f6d 100644
--- a/utf7/encoder.go
+++ b/utf7/encoder.go
@@ -12,7 +12,7 @@ var Encoder = &encoding.Encoder{
 	Transformer: &encoder{},
 }
 
-type encoder struct {}
+type encoder struct{}
 
 func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
 	for i := 0; i < len(src); {
@@ -43,7 +43,7 @@ func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err er
 			b = encode(src[start:i])
 		}
 
-		if nDst + len(b) > len(dst) {
+		if nDst+len(b) > len(dst) {
 			err = transform.ErrShortDst
 			return
 		}
diff --git a/write.go b/write.go
new file mode 100644
index 0000000..f2c1241
--- /dev/null
+++ b/write.go
@@ -0,0 +1,238 @@
+package imap
+
+import (
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+	"time"
+	"unicode"
+)
+
+// A string that will be quoted.
+type Quoted string
+
+// An IMAP writer.
+type Writer interface {
+	io.Writer
+
+	Flush() error
+
+	writer() *writer
+}
+
+type WriterTo interface {
+	WriteTo(w Writer) error
+}
+
+func formatNumber(num uint32) string {
+	return strconv.FormatUint(uint64(num), 10)
+}
+
+func FormatDate(t time.Time) string {
+	return t.Format("2-Jan-2006")
+}
+
+func FormatDateTime(t time.Time) string {
+	return t.Format("2-Jan-2006 15:04:05 -0700")
+}
+
+// Convert a string list to a field list.
+func FormatStringList(list []string) (fields []interface{}) {
+	fields = make([]interface{}, len(list))
+	for i, v := range list {
+		fields[i] = v
+	}
+	return
+}
+
+// Check if a string is 8-bit clean.
+func isAscii(s string) bool {
+	for _, c := range s {
+		if c > unicode.MaxASCII || unicode.IsControl(c) {
+			return false
+		}
+	}
+	return true
+}
+
+type writer struct {
+	io.Writer
+
+	continues <-chan bool
+}
+
+func (w *writer) writer() *writer {
+	return w
+}
+
+// Helper function to write a string to w.
+func (w *writer) writeString(s string) error {
+	_, err := io.WriteString(w.Writer, s)
+	return err
+}
+
+func (w *writer) writeCrlf() error {
+	if err := w.writeString(crlf); err != nil {
+		return err
+	}
+
+	return w.Flush()
+}
+
+func (w *writer) writeNumber(num uint32) error {
+	return w.writeString(formatNumber(num))
+}
+
+func (w *writer) writeQuoted(s string) error {
+	return w.writeString(strconv.Quote(s))
+}
+
+func (w *writer) writeAtom(s string) error {
+	return w.writeString(s)
+}
+
+func (w *writer) writeAstring(s string) error {
+	if !isAscii(s) {
+		// IMAP doesn't allow 8-bit data outside literals
+		return w.writeLiteral(NewLiteral([]byte(s)))
+	}
+
+	specials := string([]rune{dquote, listStart, listEnd, literalStart, sp})
+	if strings.ToUpper(s) == nilAtom || s == "" || strings.ContainsAny(s, specials) {
+		return w.writeQuoted(s)
+	}
+
+	return w.writeAtom(s)
+}
+
+func (w *writer) writeDateTime(t time.Time) error {
+	if t.IsZero() {
+		return w.writeAtom(nilAtom)
+	}
+	return w.writeQuoted(FormatDateTime(t))
+}
+
+func (w *writer) writeFields(fields []interface{}) error {
+	for i, field := range fields {
+		if i > 0 { // Write separator
+			if err := w.writeString(string(sp)); err != nil {
+				return err
+			}
+		}
+
+		if err := w.writeField(field); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (w *writer) writeList(fields []interface{}) error {
+	if err := w.writeString(string(listStart)); err != nil {
+		return err
+	}
+
+	if err := w.writeFields(fields); err != nil {
+		return err
+	}
+
+	return w.writeString(string(listEnd))
+}
+
+func (w *writer) writeLiteral(l *Literal) error {
+	if l == nil {
+		return w.writeString(nilAtom)
+	}
+
+	header := string(literalStart) + strconv.Itoa(l.Len()) + string(literalEnd) + crlf
+	if err := w.writeString(header); err != nil {
+		return err
+	}
+
+	// If a channel is available, wait for a continuation request before sending data
+	if w.continues != nil {
+		// Make sure to flush the writer, otherwise we may never receive a continuation request
+		if err := w.Flush(); err != nil {
+			return err
+		}
+
+		if !<-w.continues {
+			return fmt.Errorf("imap: cannot send literal: no continuation request received")
+		}
+	}
+
+	_, err := w.Write(l.Bytes())
+	return err
+}
+
+func (w *writer) writeField(field interface{}) error {
+	if field == nil {
+		return w.writeAtom(nilAtom)
+	}
+
+	switch field := field.(type) {
+	case string:
+		return w.writeAstring(field)
+	case Quoted:
+		return w.writeQuoted(string(field))
+	case int:
+		return w.writeNumber(uint32(field))
+	case uint32:
+		return w.writeNumber(field)
+	case *Literal:
+		return w.writeLiteral(field)
+	case []interface{}:
+		return w.writeList(field)
+	case time.Time:
+		return w.writeDateTime(field)
+	case *SeqSet:
+		return w.writeString(field.String())
+	case *BodySectionName:
+		return w.writeString(field.String())
+	}
+
+	return fmt.Errorf("imap: cannot format field: %v", field)
+}
+
+func (w *writer) writeRespCode(code string, args []interface{}) error {
+	if err := w.writeString(string(respCodeStart)); err != nil {
+		return err
+	}
+
+	fields := []interface{}{code}
+	fields = append(fields, args...)
+
+	if err := w.writeFields(fields); err != nil {
+		return err
+	}
+
+	return w.writeString(string(respCodeEnd))
+}
+
+func (w *writer) writeLine(fields ...interface{}) error {
+	if err := w.writeFields(fields); err != nil {
+		return err
+	}
+
+	return w.writeCrlf()
+}
+
+func (w *writer) Flush() error {
+	f, ok := w.Writer.(interface {
+		Flush() error
+	})
+	if ok {
+		return f.Flush()
+	}
+	return nil
+}
+
+func NewWriter(w io.Writer) Writer {
+	return &writer{Writer: w}
+}
+
+func NewClientWriter(w io.Writer, continues <-chan bool) Writer {
+	return &writer{Writer: w, continues: continues}
+}


Test patch:
diff --git a/client/client_test.go b/client/client_test.go
index f302f93..8611755 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -9,7 +9,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/client"
 )
 
@@ -24,7 +24,7 @@ func testClient(t *testing.T, ct ClientTester, st ServerTester) {
 	defer l.Close()
 
 	done := make(chan error)
-	go (func () {
+	go (func() {
 		c, err := client.Dial(l.Addr().String())
 		if err != nil {
 			done <- err
@@ -38,7 +38,7 @@ func testClient(t *testing.T, ct ClientTester, st ServerTester) {
 			return
 		}
 
-		c.State = common.LogoutState
+		c.State = imap.LogoutState
 		done <- nil
 	})()
 
diff --git a/client/cmd_any_test.go b/client/cmd_any_test.go
index 0d7b84e..6de4327 100644
--- a/client/cmd_any_test.go
+++ b/client/cmd_any_test.go
@@ -31,7 +31,7 @@ func TestClient_Capability(t *testing.T) {
 		}
 
 		io.WriteString(c, "* CAPABILITY IMAP4rev1 XTEST\r\n")
-		io.WriteString(c, tag + " OK CAPABILITY completed.\r\n")
+		io.WriteString(c, tag+" OK CAPABILITY completed.\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -51,7 +51,7 @@ func TestClient_Noop(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK NOOP completed\r\n")
+		io.WriteString(c, tag+" OK NOOP completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -72,7 +72,7 @@ func TestClient_Logout(t *testing.T) {
 		}
 
 		io.WriteString(c, "* BYE Client asked to close connection.\r\n")
-		io.WriteString(c, tag + " OK LOGOUT completed.\r\n")
+		io.WriteString(c, tag+" OK LOGOUT completed.\r\n")
 		//c.Close()
 	}
 
diff --git a/client/cmd_auth_test.go b/client/cmd_auth_test.go
index b4d8459..0951d4a 100644
--- a/client/cmd_auth_test.go
+++ b/client/cmd_auth_test.go
@@ -7,13 +7,13 @@ import (
 	"testing"
 	"time"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/client"
 )
 
 func TestClient_Select(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
 		mbox, err := c.Select("INBOX", false)
 		if err != nil {
@@ -65,7 +65,7 @@ func TestClient_Select(t *testing.T) {
 		io.WriteString(c, "* OK [UIDNEXT 4392] Predicted next UID\r\n")
 		io.WriteString(c, "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n")
 		io.WriteString(c, "* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)] Limited\r\n")
-		io.WriteString(c, tag + " OK SELECT completed\r\n")
+		io.WriteString(c, tag+" OK SELECT completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -73,7 +73,7 @@ func TestClient_Select(t *testing.T) {
 
 func TestClient_Select_ReadOnly(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
 		mbox, err := c.Select("INBOX", true)
 		if err != nil {
@@ -97,7 +97,7 @@ func TestClient_Select_ReadOnly(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK [READ-ONLY] EXAMINE completed\r\n")
+		io.WriteString(c, tag+" OK [READ-ONLY] EXAMINE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -105,7 +105,7 @@ func TestClient_Select_ReadOnly(t *testing.T) {
 
 func TestClient_Create(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
 		err = c.Create("New Mailbox")
 		return
@@ -119,7 +119,7 @@ func TestClient_Create(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK CREATE completed\r\n")
+		io.WriteString(c, tag+" OK CREATE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -127,7 +127,7 @@ func TestClient_Create(t *testing.T) {
 
 func TestClient_Delete(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
 		err = c.Delete("Old Mailbox")
 		return
@@ -141,7 +141,7 @@ func TestClient_Delete(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK DELETE completed\r\n")
+		io.WriteString(c, tag+" OK DELETE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -149,7 +149,7 @@ func TestClient_Delete(t *testing.T) {
 
 func TestClient_Rename(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
 		err = c.Rename("Old Mailbox", "New Mailbox")
 		return
@@ -163,7 +163,7 @@ func TestClient_Rename(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK RENAME completed\r\n")
+		io.WriteString(c, tag+" OK RENAME completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -171,7 +171,7 @@ func TestClient_Rename(t *testing.T) {
 
 func TestClient_Subscribe(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
 		err = c.Subscribe("Mailbox")
 		return
@@ -185,7 +185,7 @@ func TestClient_Subscribe(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK SUBSCRIBE completed\r\n")
+		io.WriteString(c, tag+" OK SUBSCRIBE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -193,7 +193,7 @@ func TestClient_Subscribe(t *testing.T) {
 
 func TestClient_Unsubscribe(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
 		err = c.Unsubscribe("Mailbox")
 		return
@@ -207,7 +207,7 @@ func TestClient_Unsubscribe(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK UNSUBSCRIBE completed\r\n")
+		io.WriteString(c, tag+" OK UNSUBSCRIBE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -215,16 +215,16 @@ func TestClient_Unsubscribe(t *testing.T) {
 
 func TestClient_List(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
-		mailboxes := make(chan *common.MailboxInfo, 3)
+		mailboxes := make(chan *imap.MailboxInfo, 3)
 		err = c.List("", "%", mailboxes)
 		if err != nil {
 			return
 		}
 
-		expected := []struct{
-			name string
+		expected := []struct {
+			name       string
 			attributes []string
 		}{
 			{"INBOX", []string{"flag1"}},
@@ -259,7 +259,7 @@ func TestClient_List(t *testing.T) {
 		io.WriteString(c, "* LIST (flag1) \"/\" INBOX\r\n")
 		io.WriteString(c, "* LIST (flag2 flag3) \"/\" Drafts\r\n")
 		io.WriteString(c, "* LIST () \"/\" Sent\r\n")
-		io.WriteString(c, tag + " OK LIST completed\r\n")
+		io.WriteString(c, tag+" OK LIST completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -267,9 +267,9 @@ func TestClient_List(t *testing.T) {
 
 func TestClient_Lsub(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
-		mailboxes := make(chan *common.MailboxInfo, 1)
+		mailboxes := make(chan *imap.MailboxInfo, 1)
 		err = c.Lsub("", "%", mailboxes)
 		if err != nil {
 			return
@@ -295,7 +295,7 @@ func TestClient_Lsub(t *testing.T) {
 		}
 
 		io.WriteString(c, "* LSUB () \"/\" INBOX\r\n")
-		io.WriteString(c, tag + " OK LIST completed\r\n")
+		io.WriteString(c, tag+" OK LIST completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -303,7 +303,7 @@ func TestClient_Lsub(t *testing.T) {
 
 func TestClient_Status(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
 		mbox, err := c.Status("INBOX", []string{"MESSAGES", "RECENT"})
 		if err != nil {
@@ -329,7 +329,7 @@ func TestClient_Status(t *testing.T) {
 		}
 
 		io.WriteString(c, "* STATUS INBOX (MESSAGES 42 RECENT 1)\r\n")
-		io.WriteString(c, tag + " OK STATUS completed\r\n")
+		io.WriteString(c, tag+" OK STATUS completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -339,11 +339,11 @@ func TestClient_Append(t *testing.T) {
 	msg := "Hello World!\r\nHello Gophers!\r\n"
 
 	ct := func(c *client.Client) (err error) {
-		c.State = common.AuthenticatedState
+		c.State = imap.AuthenticatedState
 
 		date := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
-		literal := common.NewLiteral([]byte(msg))
-		err = c.Append("INBOX", []string{"\\Seen", "\\Draft"}, &date, literal)
+		literal := imap.NewLiteral([]byte(msg))
+		err = c.Append("INBOX", []string{"\\Seen", "\\Draft"}, date, literal)
 		return
 	}
 
@@ -366,7 +366,7 @@ func TestClient_Append(t *testing.T) {
 			t.Fatal("Bad literal:", string(b))
 		}
 
-		io.WriteString(c, tag + " OK APPEND completed\r\n")
+		io.WriteString(c, tag+" OK APPEND completed\r\n")
 	}
 
 	testClient(t, ct, st)
diff --git a/client/cmd_noauth_test.go b/client/cmd_noauth_test.go
index 430d02c..d03dd50 100644
--- a/client/cmd_noauth_test.go
+++ b/client/cmd_noauth_test.go
@@ -4,12 +4,12 @@ import (
 	"bytes"
 	"crypto/tls"
 	"encoding/base64"
-	"io"
 	"fmt"
+	"io"
 	"net"
 	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/client"
 	"github.com/emersion/go-imap/internal"
 	"github.com/emersion/go-sasl"
@@ -23,7 +23,7 @@ func TestClient_StartTLS(t *testing.T) {
 
 	tlsConfig := &tls.Config{
 		InsecureSkipVerify: true,
-		Certificates: []tls.Certificate{cert},
+		Certificates:       []tls.Certificate{cert},
 	}
 
 	ct := func(c *client.Client) (err error) {
@@ -59,7 +59,7 @@ func TestClient_StartTLS(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK Begin TLS negotiation now\r\n")
+		io.WriteString(c, tag+" OK Begin TLS negotiation now\r\n")
 
 		sc := tls.Server(c, tlsConfig)
 		if err = sc.Handshake(); err != nil {
@@ -74,7 +74,7 @@ func TestClient_StartTLS(t *testing.T) {
 		}
 
 		io.WriteString(sc, "* CAPABILITY IMAP4rev1 AUTH=PLAIN\r\n")
-		io.WriteString(sc, tag + " OK CAPABILITY completed.\r\n")
+		io.WriteString(sc, tag+" OK CAPABILITY completed.\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -94,7 +94,7 @@ func TestClient_Authenticate(t *testing.T) {
 			return
 		}
 
-		if c.State != common.AuthenticatedState {
+		if c.State != imap.AuthenticatedState {
 			return fmt.Errorf("Client is not in authenticated state after AUTENTICATE")
 		}
 
@@ -128,7 +128,7 @@ func TestClient_Authenticate(t *testing.T) {
 			t.Fatal("Bad password")
 		}
 
-		io.WriteString(c, tag + " OK AUTHENTICATE completed\r\n")
+		io.WriteString(c, tag+" OK AUTHENTICATE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -141,7 +141,7 @@ func TestClient_Login_Success(t *testing.T) {
 			return
 		}
 
-		if c.State != common.AuthenticatedState {
+		if c.State != imap.AuthenticatedState {
 			return fmt.Errorf("Client is not in authenticated state after login")
 		}
 
@@ -156,7 +156,7 @@ func TestClient_Login_Success(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK LOGIN completed\r\n")
+		io.WriteString(c, tag+" OK LOGIN completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -169,7 +169,7 @@ func TestClient_Login_Error(t *testing.T) {
 			return fmt.Errorf("Failed login didn't returned an error: %v", err)
 		}
 
-		if c.State != common.NotAuthenticatedState {
+		if c.State != imap.NotAuthenticatedState {
 			return fmt.Errorf("Client state must be NotAuthenticated after failed login, but is: %v", c.State)
 		}
 
@@ -184,7 +184,7 @@ func TestClient_Login_Error(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " NO LOGIN incorrect\r\n")
+		io.WriteString(c, tag+" NO LOGIN incorrect\r\n")
 	}
 
 	testClient(t, ct, st)
diff --git a/client/cmd_selected_test.go b/client/cmd_selected_test.go
index cf6bf14..59a9cc0 100644
--- a/client/cmd_selected_test.go
+++ b/client/cmd_selected_test.go
@@ -1,18 +1,18 @@
 package client_test
 
 import (
-	"io"
 	"fmt"
+	"io"
 	"net"
 	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/client"
 )
 
 func TestClient_Check(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
 		err = c.Check()
 		return
@@ -26,7 +26,7 @@ func TestClient_Check(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK CHECK completed\r\n")
+		io.WriteString(c, tag+" OK CHECK completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -34,15 +34,15 @@ func TestClient_Check(t *testing.T) {
 
 func TestClient_Close(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
-		c.Mailbox = &common.MailboxStatus{Name: "INBOX"}
+		c.State = imap.SelectedState
+		c.Mailbox = &imap.MailboxStatus{Name: "INBOX"}
 
 		err = c.Close()
 		if err != nil {
 			return
 		}
 
-		if c.State != common.AuthenticatedState {
+		if c.State != imap.AuthenticatedState {
 			return fmt.Errorf("Bad client state: %v", c.State)
 		}
 		if c.Mailbox != nil {
@@ -59,7 +59,7 @@ func TestClient_Close(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK CLOSE completed\r\n")
+		io.WriteString(c, tag+" OK CLOSE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -67,7 +67,7 @@ func TestClient_Close(t *testing.T) {
 
 func TestClient_Expunge(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
 		expunged := make(chan uint32, 4)
 		err = c.Expunge(expunged)
@@ -99,7 +99,7 @@ func TestClient_Expunge(t *testing.T) {
 		io.WriteString(c, "* 3 EXPUNGE\r\n")
 		io.WriteString(c, "* 5 EXPUNGE\r\n")
 		io.WriteString(c, "* 8 EXPUNGE\r\n")
-		io.WriteString(c, tag + " OK EXPUNGE completed\r\n")
+		io.WriteString(c, tag+" OK EXPUNGE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -107,14 +107,14 @@ func TestClient_Expunge(t *testing.T) {
 
 func TestClient_Search(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
-		date, _ := common.ParseSearchDate("1-Feb-1994")
-		criteria := &common.SearchCriteria{
+		date, _ := imap.ParseDate("1-Feb-1994")
+		criteria := &imap.SearchCriteria{
 			Deleted: true,
-			From: "Smith",
-			Since: date,
-			Not: &common.SearchCriteria{To: "Pauline"},
+			From:    "Smith",
+			Since:   date,
+			Not:     &imap.SearchCriteria{To: "Pauline"},
 		}
 
 		results, err := c.Search(criteria)
@@ -138,7 +138,7 @@ func TestClient_Search(t *testing.T) {
 		}
 
 		io.WriteString(c, "* SEARCH 2 84 882\r\n")
-		io.WriteString(c, tag + " OK SEARCH completed\r\n")
+		io.WriteString(c, tag+" OK SEARCH completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -146,9 +146,9 @@ func TestClient_Search(t *testing.T) {
 
 func TestClient_Search_Uid(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
-		criteria := &common.SearchCriteria{
+		criteria := &imap.SearchCriteria{
 			Undeleted: true,
 		}
 
@@ -173,7 +173,7 @@ func TestClient_Search_Uid(t *testing.T) {
 		}
 
 		io.WriteString(c, "* SEARCH 1 78 2010\r\n")
-		io.WriteString(c, tag + " OK UID SEARCH completed\r\n")
+		io.WriteString(c, tag+" OK UID SEARCH completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -181,11 +181,11 @@ func TestClient_Search_Uid(t *testing.T) {
 
 func TestClient_Fetch(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
-		seqset, _ := common.NewSeqSet("2:3")
+		seqset, _ := imap.NewSeqSet("2:3")
 		fields := []string{"UID", "BODY[]"}
-		messages := make(chan *common.Message, 2)
+		messages := make(chan *imap.Message, 2)
 
 		err = c.Fetch(seqset, fields, messages)
 		if err != nil {
@@ -235,7 +235,7 @@ func TestClient_Fetch(t *testing.T) {
 		io.WriteString(c, "Hello World!")
 		io.WriteString(c, ")\r\n")
 
-		io.WriteString(c, tag + " OK FETCH completed\r\n")
+		io.WriteString(c, tag+" OK FETCH completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -243,11 +243,11 @@ func TestClient_Fetch(t *testing.T) {
 
 func TestClient_Fetch_Uid(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
-		seqset, _ := common.NewSeqSet("1:867")
+		seqset, _ := imap.NewSeqSet("1:867")
 		fields := []string{"FLAGS"}
-		messages := make(chan *common.Message, 1)
+		messages := make(chan *imap.Message, 1)
 
 		err = c.UidFetch(seqset, fields, messages)
 		if err != nil {
@@ -278,7 +278,7 @@ func TestClient_Fetch_Uid(t *testing.T) {
 
 		io.WriteString(c, "* 23 FETCH (UID 42 FLAGS (\\Seen))\r\n")
 
-		io.WriteString(c, tag + " OK UID FETCH completed\r\n")
+		io.WriteString(c, tag+" OK UID FETCH completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -286,11 +286,11 @@ func TestClient_Fetch_Uid(t *testing.T) {
 
 func TestClient_Store(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
-		updates := make(chan *common.Message, 1)
-		seqset, _ := common.NewSeqSet("2")
-		err = c.Store(seqset, common.AddFlags, []interface{}{"\\Seen"}, updates)
+		updates := make(chan *imap.Message, 1)
+		seqset, _ := imap.NewSeqSet("2")
+		err = c.Store(seqset, imap.AddFlags, []interface{}{"\\Seen"}, updates)
 		if err != nil {
 			return
 		}
@@ -312,7 +312,7 @@ func TestClient_Store(t *testing.T) {
 		}
 
 		io.WriteString(c, "* 2 FETCH (FLAGS (\\Seen))\r\n")
-		io.WriteString(c, tag + " OK STORE completed\r\n")
+		io.WriteString(c, tag+" OK STORE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -320,10 +320,10 @@ func TestClient_Store(t *testing.T) {
 
 func TestClient_Store_Silent(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
-		seqset, _ := common.NewSeqSet("2:3")
-		err = c.Store(seqset, common.AddFlags, []interface{}{"\\Seen"}, nil)
+		seqset, _ := imap.NewSeqSet("2:3")
+		err = c.Store(seqset, imap.AddFlags, []interface{}{"\\Seen"}, nil)
 		return
 	}
 
@@ -335,7 +335,7 @@ func TestClient_Store_Silent(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK STORE completed\r\n")
+		io.WriteString(c, tag+" OK STORE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -343,10 +343,10 @@ func TestClient_Store_Silent(t *testing.T) {
 
 func TestClient_Store_Uid(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
-		seqset, _ := common.NewSeqSet("27:901")
-		err = c.UidStore(seqset, common.AddFlags, []interface{}{"\\Deleted"}, nil)
+		seqset, _ := imap.NewSeqSet("27:901")
+		err = c.UidStore(seqset, imap.AddFlags, []interface{}{"\\Deleted"}, nil)
 		return
 	}
 
@@ -358,7 +358,7 @@ func TestClient_Store_Uid(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK UID STORE completed\r\n")
+		io.WriteString(c, tag+" OK UID STORE completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -366,9 +366,9 @@ func TestClient_Store_Uid(t *testing.T) {
 
 func TestClient_Copy(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
-		seqset, _ := common.NewSeqSet("2:4")
+		seqset, _ := imap.NewSeqSet("2:4")
 		err = c.Copy(seqset, "Sent")
 		return
 	}
@@ -381,7 +381,7 @@ func TestClient_Copy(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK COPY completed\r\n")
+		io.WriteString(c, tag+" OK COPY completed\r\n")
 	}
 
 	testClient(t, ct, st)
@@ -389,9 +389,9 @@ func TestClient_Copy(t *testing.T) {
 
 func TestClient_Copy_Uid(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
-		c.State = common.SelectedState
+		c.State = imap.SelectedState
 
-		seqset, _ := common.NewSeqSet("78:102")
+		seqset, _ := imap.NewSeqSet("78:102")
 		err = c.UidCopy(seqset, "Drafts")
 		return
 	}
@@ -404,7 +404,7 @@ func TestClient_Copy_Uid(t *testing.T) {
 			t.Fatal("Bad command:", cmd)
 		}
 
-		io.WriteString(c, tag + " OK UID COPY completed\r\n")
+		io.WriteString(c, tag+" OK UID COPY completed\r\n")
 	}
 
 	testClient(t, ct, st)
diff --git a/common/command_test.go b/command_test.go
similarity index 80%
rename from common/command_test.go
rename to command_test.go
index 4b36716..ef5f6f3 100644
--- a/common/command_test.go
+++ b/command_test.go
@@ -1,15 +1,15 @@
-package common_test
+package imap_test
 
 import (
 	"bytes"
 	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 func TestCommand_Command(t *testing.T) {
-	cmd := &common.Command{
-		Tag: "A001",
+	cmd := &imap.Command{
+		Tag:  "A001",
 		Name: "NOOP",
 	}
 
@@ -20,14 +20,14 @@ func TestCommand_Command(t *testing.T) {
 
 func TestCommand_WriteTo_NoArgs(t *testing.T) {
 	var b bytes.Buffer
-	w := common.NewWriter(&b)
+	w := imap.NewWriter(&b)
 
-	cmd := &common.Command{
-		Tag: "A001",
+	cmd := &imap.Command{
+		Tag:  "A001",
 		Name: "NOOP",
 	}
 
-	if _, err := cmd.WriteTo(w); err != nil {
+	if err := cmd.WriteTo(w); err != nil {
 		t.Fatal(err)
 	}
 	if b.String() != "A001 NOOP\r\n" {
@@ -37,15 +37,15 @@ func TestCommand_WriteTo_NoArgs(t *testing.T) {
 
 func TestCommand_WriteTo_WithArgs(t *testing.T) {
 	var b bytes.Buffer
-	w := common.NewWriter(&b)
+	w := imap.NewWriter(&b)
 
-	cmd := &common.Command{
-		Tag: "A002",
-		Name: "LOGIN",
+	cmd := &imap.Command{
+		Tag:       "A002",
+		Name:      "LOGIN",
 		Arguments: []interface{}{"username", "password"},
 	}
 
-	if _, err := cmd.WriteTo(w); err != nil {
+	if err := cmd.WriteTo(w); err != nil {
 		t.Fatal(err)
 	}
 	if b.String() != "A002 LOGIN username password\r\n" {
@@ -56,7 +56,7 @@ func TestCommand_WriteTo_WithArgs(t *testing.T) {
 func TestCommand_Parse_NoArgs(t *testing.T) {
 	fields := []interface{}{"a", "NOOP"}
 
-	cmd := &common.Command{}
+	cmd := &imap.Command{}
 
 	if err := cmd.Parse(fields); err != nil {
 		t.Fatal(err)
@@ -75,7 +75,7 @@ func TestCommand_Parse_NoArgs(t *testing.T) {
 func TestCommand_Parse_WithArgs(t *testing.T) {
 	fields := []interface{}{"a", "LOGIN", "username", "password"}
 
-	cmd := &common.Command{}
+	cmd := &imap.Command{}
 
 	if err := cmd.Parse(fields); err != nil {
 		t.Fatal(err)
diff --git a/common/conn_test.go b/conn_test.go
similarity index 85%
rename from common/conn_test.go
rename to conn_test.go
index d40097b..0b3cc68 100644
--- a/common/conn_test.go
+++ b/conn_test.go
@@ -1,4 +1,4 @@
-package common_test
+package imap_test
 
 import (
 	"bytes"
@@ -6,7 +6,7 @@ import (
 	"net"
 	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 func TestNewConn(t *testing.T) {
@@ -19,10 +19,10 @@ func TestNewConn(t *testing.T) {
 		done <- err
 	})()
 
-	r := common.NewReader(nil)
-	w := common.NewWriter(nil)
+	r := imap.NewReader(nil)
+	w := imap.NewWriter(nil)
 
-	ic := common.NewConn(c, r, w)
+	ic := imap.NewConn(c, r, w)
 
 	sent := []byte("hi")
 	ic.Write(sent)
@@ -73,10 +73,10 @@ func TestConn_Upgrade(t *testing.T) {
 		done <- err
 	})()
 
-	r := common.NewReader(nil)
-	w := common.NewWriter(nil)
+	r := imap.NewReader(nil)
+	w := imap.NewWriter(nil)
 
-	ic := common.NewConn(c, r, w)
+	ic := imap.NewConn(c, r, w)
 
 	began := make(chan struct{})
 	go ic.Upgrade(func(conn net.Conn) (net.Conn, error) {
diff --git a/common/handle_test.go b/handle_test.go
similarity index 82%
rename from common/handle_test.go
rename to handle_test.go
index 4cca93c..ed89867 100644
--- a/common/handle_test.go
+++ b/handle_test.go
@@ -1,14 +1,14 @@
-package common_test
+package imap_test
 
 import (
 	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 func TestRespHandling_Accept(t *testing.T) {
 	ch := make(chan bool, 1)
-	hdlr := &common.RespHandling{
+	hdlr := &imap.RespHandling{
 		Accepts: ch,
 	}
 
@@ -22,7 +22,7 @@ func TestRespHandling_Accept(t *testing.T) {
 
 func TestRespHandling_Reject(t *testing.T) {
 	ch := make(chan bool, 1)
-	hdlr := &common.RespHandling{
+	hdlr := &imap.RespHandling{
 		Accepts: ch,
 	}
 
@@ -36,9 +36,9 @@ func TestRespHandling_Reject(t *testing.T) {
 
 func TestRespHandling_AcceptNamedResp_Matching(t *testing.T) {
 	ch := make(chan bool, 1)
-	hdlr := &common.RespHandling{
-		Resp: &common.Resp{
-			Tag: "*",
+	hdlr := &imap.RespHandling{
+		Resp: &imap.Resp{
+			Tag:    "*",
 			Fields: []interface{}{"SEARCH", "42"},
 		},
 		Accepts: ch,
@@ -63,9 +63,9 @@ func TestRespHandling_AcceptNamedResp_Matching(t *testing.T) {
 
 func TestRespHandling_AcceptNamedResp_NotMatching(t *testing.T) {
 	ch := make(chan bool, 1)
-	hdlr := &common.RespHandling{
-		Resp: &common.Resp{
-			Tag: "*",
+	hdlr := &imap.RespHandling{
+		Resp: &imap.Resp{
+			Tag:    "*",
 			Fields: []interface{}{"26", "EXISTS"},
 		},
 		Accepts: ch,
diff --git a/common/literal_test.go b/literal_test.go
similarity index 95%
rename from common/literal_test.go
rename to literal_test.go
index 93522b8..c3bc87a 100644
--- a/common/literal_test.go
+++ b/literal_test.go
@@ -1,4 +1,4 @@
-package common
+package imap
 
 import (
 	"testing"
diff --git a/common/mailbox_test.go b/mailbox_test.go
similarity index 75%
rename from common/mailbox_test.go
rename to mailbox_test.go
index 08b1e92..134de48 100644
--- a/common/mailbox_test.go
+++ b/mailbox_test.go
@@ -1,10 +1,10 @@
-package common_test
+package imap_test
 
 import (
-	"testing"
 	"fmt"
+	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 func TestMailboxInfo(t *testing.T) {
@@ -13,18 +13,18 @@ func TestMailboxInfo(t *testing.T) {
 		"/",
 		"INBOX",
 	}
-	info := &common.MailboxInfo{
+	info := &imap.MailboxInfo{
 		Attributes: []string{"\\Noselect", "\\Recent", "\\Unseen"},
-		Delimiter: "/",
-		Name: "INBOX",
+		Delimiter:  "/",
+		Name:       "INBOX",
 	}
 
 	testMailboxInfo_Parse(t, fields, info)
 	testMailboxInfo_Format(t, info, fields)
 }
 
-func testMailboxInfo_Parse(t *testing.T, input []interface{}, expected *common.MailboxInfo) {
-	output := &common.MailboxInfo{}
+func testMailboxInfo_Parse(t *testing.T, input []interface{}, expected *imap.MailboxInfo) {
+	output := &imap.MailboxInfo{}
 	if err := output.Parse(input); err != nil {
 		t.Fatal(err)
 	}
@@ -40,7 +40,7 @@ func testMailboxInfo_Parse(t *testing.T, input []interface{}, expected *common.M
 	}
 }
 
-func testMailboxInfo_Format(t *testing.T, input *common.MailboxInfo, expected []interface{}) {
+func testMailboxInfo_Format(t *testing.T, input *imap.MailboxInfo, expected []interface{}) {
 	output := input.Format()
 
 	if fmt.Sprint(output) != fmt.Sprint(expected) {
diff --git a/common/message_test.go b/message_test.go
similarity index 59%
rename from common/message_test.go
rename to message_test.go
index a02fcb3..5dae9e8 100644
--- a/common/message_test.go
+++ b/message_test.go
@@ -1,4 +1,4 @@
-package common_test
+package imap
 
 import (
 	"bytes"
@@ -6,15 +6,13 @@ import (
 	"reflect"
 	"testing"
 	"time"
-
-	"github.com/emersion/go-imap/common"
 )
 
 func formatFields(fields []interface{}) (string, error) {
 	b := &bytes.Buffer{}
-	w := common.NewWriter(b)
+	w := NewWriter(b).writer()
 
-	if _, err := w.WriteList(fields); err != nil {
+	if err := w.writeList(fields); err != nil {
 		return "", fmt.Errorf("Cannot format %v: %v", fields, err)
 	}
 
@@ -22,7 +20,7 @@ func formatFields(fields []interface{}) (string, error) {
 }
 
 func TestParseDate(t *testing.T) {
-	tests := []struct{
+	tests := []struct {
 		dateStr string
 		exp     time.Time
 	}{
@@ -32,7 +30,7 @@ func TestParseDate(t *testing.T) {
 		},
 	}
 	for _, test := range tests {
-		date, err := common.ParseDate(test.dateStr)
+		date, err := ParseDateTime(test.dateStr)
 		if err != nil {
 			t.Errorf("Failed parsing %q: %v", test.dateStr, err)
 			continue
@@ -43,24 +41,24 @@ func TestParseDate(t *testing.T) {
 	}
 }
 
-var messageTests = []struct{
-	message *common.Message
-	fields []interface{}
+var messageTests = []struct {
+	message *Message
+	fields  []interface{}
 }{
 	{
-		message: &common.Message{
-			Items: []string{"ENVELOPE", "BODYSTRUCTURE", "FLAGS", "RFC822.SIZE", "UID"},
-			Body: map[*common.BodySectionName]*common.Literal{},
-			Envelope: envelopeTests[0].envelope,
+		message: &Message{
+			Items:         []string{"ENVELOPE", "BODYSTRUCTURE", "FLAGS", "RFC822.SIZE", "UID"},
+			Body:          map[*BodySectionName]*Literal{},
+			Envelope:      envelopeTests[0].envelope,
 			BodyStructure: bodyStructureTests[0].bodyStructure,
-			Flags: []string{common.SeenFlag, common.AnsweredFlag},
-			Size: 4242,
-			Uid: 2424,
+			Flags:         []string{SeenFlag, AnsweredFlag},
+			Size:          4242,
+			Uid:           2424,
 		},
 		fields: []interface{}{
 			"ENVELOPE", envelopeTests[0].fields,
 			"BODYSTRUCTURE", bodyStructureTests[0].fields,
-			"FLAGS", []interface{}{common.SeenFlag, common.AnsweredFlag},
+			"FLAGS", []interface{}{SeenFlag, AnsweredFlag},
 			"RFC822.SIZE", "4242",
 			"UID", "2424",
 		},
@@ -69,7 +67,7 @@ var messageTests = []struct{
 
 func TestMessage_Parse(t *testing.T) {
 	for i, test := range messageTests {
-		m := common.NewMessage()
+		m := NewMessage()
 		if err := m.Parse(test.fields); err != nil {
 			t.Errorf("Cannot parse message for #%v:", i, err)
 		} else if !reflect.DeepEqual(m, test.message) {
@@ -96,71 +94,71 @@ func TestMessage_Format(t *testing.T) {
 	}
 }
 
-var bodySectionNameTests = []struct{
-	raw string
-	parsed *common.BodySectionName
+var bodySectionNameTests = []struct {
+	raw       string
+	parsed    *BodySectionName
 	formatted string
 }{
 	{
-		raw: "BODY[]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{}},
+		raw:    "BODY[]",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{}},
 	},
 	{
-		raw: "RFC822",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{}},
+		raw:       "RFC822",
+		parsed:    &BodySectionName{BodyPartName: &BodyPartName{}},
 		formatted: "BODY[]",
 	},
 	{
-		raw: "BODY[HEADER]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier}},
+		raw:    "BODY[HEADER]",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier}},
 	},
 	{
-		raw: "BODY.PEEK[]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{}, Peek: true},
+		raw:    "BODY.PEEK[]",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{}, Peek: true},
 	},
 	{
-		raw: "BODY[TEXT]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.TextSpecifier}},
+		raw:    "BODY[TEXT]",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: TextSpecifier}},
 	},
 	{
-		raw: "RFC822.TEXT",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.TextSpecifier}},
+		raw:       "RFC822.TEXT",
+		parsed:    &BodySectionName{BodyPartName: &BodyPartName{Specifier: TextSpecifier}},
 		formatted: "BODY[TEXT]",
 	},
 	{
-		raw: "RFC822.HEADER",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier}, Peek: true},
+		raw:       "RFC822.HEADER",
+		parsed:    &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier}, Peek: true},
 		formatted: "BODY.PEEK[HEADER]",
 	},
 	{
-		raw: "BODY[]<0.512>",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{}, Partial: []int{0, 512}},
+		raw:    "BODY[]<0.512>",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{}, Partial: []int{0, 512}},
 	},
 	{
-		raw: "BODY[1.2.3]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Path: []int{1, 2, 3}}},
+		raw:    "BODY[1.2.3]",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Path: []int{1, 2, 3}}},
 	},
 	{
-		raw: "BODY[1.2.3.HEADER]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier, Path: []int{1, 2, 3}}},
+		raw:    "BODY[1.2.3.HEADER]",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier, Path: []int{1, 2, 3}}},
 	},
 	{
-		raw: "BODY[5.MIME]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.MimeSpecifier, Path: []int{5}}},
+		raw:    "BODY[5.MIME]",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: MimeSpecifier, Path: []int{5}}},
 	},
 	{
-		raw: "BODY[HEADER.FIELDS (From To)]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier, Fields: []string{"From", "To"}}},
+		raw:    "BODY[HEADER.FIELDS (From To)]",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier, Fields: []string{"From", "To"}}},
 	},
 	{
-		raw: "BODY[HEADER.FIELDS.NOT (Content-Id)]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier, Fields: []string{"Content-Id"}, NotFields: true}},
+		raw:    "BODY[HEADER.FIELDS.NOT (Content-Id)]",
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier, Fields: []string{"Content-Id"}, NotFields: true}},
 	},
 }
 
 func TestNewBodySectionName(t *testing.T) {
 	for i, test := range bodySectionNameTests {
-		bsn, err := common.NewBodySectionName(test.raw)
+		bsn, err := NewBodySectionName(test.raw)
 		if err != nil {
 			t.Errorf("Cannot parse #%v: %v", i, err)
 			continue
@@ -192,40 +190,40 @@ func TestBodySectionName_String(t *testing.T) {
 }
 
 func TestBodySectionName_ExtractPartial(t *testing.T) {
-	tests := []struct{
-		bsn string
-		whole string
+	tests := []struct {
+		bsn     string
+		whole   string
 		partial string
 	}{
 		{
-			bsn: "BODY[]",
-			whole: "Hello World!",
+			bsn:     "BODY[]",
+			whole:   "Hello World!",
 			partial: "Hello World!",
 		},
 		{
-			bsn: "BODY[]<6.5>",
-			whole: "Hello World!",
+			bsn:     "BODY[]<6.5>",
+			whole:   "Hello World!",
 			partial: "World",
 		},
 		{
-			bsn: "BODY[]<6.1000>",
-			whole: "Hello World!",
+			bsn:     "BODY[]<6.1000>",
+			whole:   "Hello World!",
 			partial: "World!",
 		},
 		{
-			bsn: "BODY[]<0.1>",
-			whole: "Hello World!",
+			bsn:     "BODY[]<0.1>",
+			whole:   "Hello World!",
 			partial: "H",
 		},
 		{
-			bsn: "BODY[]<1000.2000>",
-			whole: "Hello World!",
+			bsn:     "BODY[]<1000.2000>",
+			whole:   "Hello World!",
 			partial: "",
 		},
 	}
 
 	for i, test := range tests {
-		bsn, err := common.NewBodySectionName(test.bsn)
+		bsn, err := NewBodySectionName(test.bsn)
 		if err != nil {
 			t.Errorf("Cannot parse body section name #%v: %v", i, err)
 			continue
@@ -240,20 +238,20 @@ func TestBodySectionName_ExtractPartial(t *testing.T) {
 
 var t = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.FixedZone("", -6*60*60))
 
-var envelopeTests = []struct{
-	envelope *common.Envelope
-	fields []interface{}
+var envelopeTests = []struct {
+	envelope *Envelope
+	fields   []interface{}
 }{
 	{
-		envelope: &common.Envelope{
-			Date: &t,
-			Subject: "Hello World!",
-			From: []*common.Address{addrTests[0].addr},
-			Sender: []*common.Address{},
-			ReplyTo: []*common.Address{},
-			To: []*common.Address{},
-			Cc: []*common.Address{},
-			Bcc: []*common.Address{},
+		envelope: &Envelope{
+			Date:      t,
+			Subject:   "Hello World!",
+			From:      []*Address{addrTests[0].addr},
+			Sender:    []*Address{},
+			ReplyTo:   []*Address{},
+			To:        []*Address{},
+			Cc:        []*Address{},
+			Bcc:       []*Address{},
 			InReplyTo: "42@example.org",
 			MessageId: "43@example.org",
 		},
@@ -274,7 +272,7 @@ var envelopeTests = []struct{
 
 func TestEnvelope_Parse(t *testing.T) {
 	for i, test := range envelopeTests {
-		e := &common.Envelope{}
+		e := &Envelope{}
 
 		if err := e.Parse(test.fields); err != nil {
 			t.Error("Error parsing envelope:", err)
@@ -302,23 +300,23 @@ func TestEnvelope_Format(t *testing.T) {
 	}
 }
 
-var addrTests = []struct{
+var addrTests = []struct {
 	fields []interface{}
-	addr *common.Address
+	addr   *Address
 }{
 	{
 		fields: []interface{}{"The NSA", nil, "root", "nsa.gov"},
-		addr: &common.Address{
+		addr: &Address{
 			PersonalName: "The NSA",
-			MailboxName: "root",
-			HostName: "nsa.gov",
+			MailboxName:  "root",
+			HostName:     "nsa.gov",
 		},
 	},
 }
 
 func TestAddress_Parse(t *testing.T) {
 	for i, test := range addrTests {
-		addr := &common.Address{}
+		addr := &Address{}
 
 		if err := addr.Parse(test.fields); err != nil {
 			t.Error("Error parsing address:", err)
@@ -339,24 +337,24 @@ func TestAddress_Format(t *testing.T) {
 
 func TestAddressList(t *testing.T) {
 	fields := make([]interface{}, len(addrTests))
-	addrs := make([]*common.Address, len(addrTests))
+	addrs := make([]*Address, len(addrTests))
 	for i, test := range addrTests {
 		fields[i] = test.fields
 		addrs[i] = test.addr
 	}
 
-	gotAddrs := common.ParseAddressList(fields)
+	gotAddrs := ParseAddressList(fields)
 	if !reflect.DeepEqual(gotAddrs, addrs) {
 		t.Error("Invalid address list: got", gotAddrs, "but expected", addrs)
 	}
 
-	gotFields := common.FormatAddressList(addrs)
+	gotFields := FormatAddressList(addrs)
 	if !reflect.DeepEqual(gotFields, fields) {
 		t.Error("Invalid address list fields: got", gotFields, "but expected", fields)
 	}
 }
 
-var paramsListTest = []struct{
+var paramsListTest = []struct {
 	fields []interface{}
 	params map[string]string
 }{
@@ -372,7 +370,7 @@ var paramsListTest = []struct{
 
 func TestParseParamList(t *testing.T) {
 	for i, test := range paramsListTest {
-		if params, err := common.ParseParamList(test.fields); err != nil {
+		if params, err := ParseParamList(test.fields); err != nil {
 			t.Errorf("Cannot parse params fields for #%v: %v", i, err)
 		} else if !reflect.DeepEqual(params, test.params) {
 			t.Errorf("Invalid params for #%v: got %v but expected %v", i, params, test.params)
@@ -382,19 +380,19 @@ func TestParseParamList(t *testing.T) {
 	// Malformed params lists
 
 	fields := []interface{}{"cc", []interface{}{"dille"}}
-	if params, err := common.ParseParamList(fields); err == nil {
+	if params, err := ParseParamList(fields); err == nil {
 		t.Error("Parsed invalid params list:", params)
 	}
 
 	fields = []interface{}{"cc"}
-	if params, err := common.ParseParamList(fields); err == nil {
+	if params, err := ParseParamList(fields); err == nil {
 		t.Error("Parsed invalid params list:", params)
 	}
 }
 
 func TestFormatParamList(t *testing.T) {
 	for i, test := range paramsListTest {
-		fields := common.FormatParamList(test.params)
+		fields := FormatParamList(test.params)
 
 		if !reflect.DeepEqual(fields, test.fields) {
 			t.Errorf("Invalid params fields for #%v: got %v but expected %v", i, fields, test.fields)
@@ -402,56 +400,56 @@ func TestFormatParamList(t *testing.T) {
 	}
 }
 
-var bodyStructureTests = []struct{
-	fields []interface{}
-	bodyStructure *common.BodyStructure
+var bodyStructureTests = []struct {
+	fields        []interface{}
+	bodyStructure *BodyStructure
 }{
 	{
 		fields: []interface{}{"image", "jpeg", []interface{}{}, "<foo4%25foo1@bar.net>", "A picture of cat", "base64", "4242"},
-		bodyStructure: &common.BodyStructure{
-			MimeType: "image",
+		bodyStructure: &BodyStructure{
+			MimeType:    "image",
 			MimeSubType: "jpeg",
-			Params: map[string]string{},
-			Id: "<foo4%25foo1@bar.net>",
+			Params:      map[string]string{},
+			Id:          "<foo4%25foo1@bar.net>",
 			Description: "A picture of cat",
-			Encoding: "base64",
-			Size: 4242,
+			Encoding:    "base64",
+			Size:        4242,
 		},
 	},
 	{
 		fields: []interface{}{"text", "plain", []interface{}{"charset", "utf-8"}, nil, nil, "us-ascii", "42", "2"},
-		bodyStructure: &common.BodyStructure{
-			MimeType: "text",
+		bodyStructure: &BodyStructure{
+			MimeType:    "text",
 			MimeSubType: "plain",
-			Params: map[string]string{"charset": "utf-8"},
-			Encoding: "us-ascii",
-			Size: 42,
-			Lines: 2,
+			Params:      map[string]string{"charset": "utf-8"},
+			Encoding:    "us-ascii",
+			Size:        42,
+			Lines:       2,
 		},
 	},
 	{
 		fields: []interface{}{
 			"message", "rfc822", []interface{}{}, nil, nil, "us-ascii", "42",
-			(&common.Envelope{}).Format(),
-			(&common.BodyStructure{}).Format(),
+			(&Envelope{}).Format(),
+			(&BodyStructure{}).Format(),
 			"67",
 		},
-		bodyStructure: &common.BodyStructure{
-			MimeType: "message",
+		bodyStructure: &BodyStructure{
+			MimeType:    "message",
 			MimeSubType: "rfc822",
-			Params: map[string]string{},
-			Encoding: "us-ascii",
-			Size: 42,
-			Lines: 67,
-			Envelope: &common.Envelope{
-				From: []*common.Address{},
-				Sender: []*common.Address{},
-				ReplyTo: []*common.Address{},
-				To: []*common.Address{},
-				Cc: []*common.Address{},
-				Bcc: []*common.Address{},
+			Params:      map[string]string{},
+			Encoding:    "us-ascii",
+			Size:        42,
+			Lines:       67,
+			Envelope: &Envelope{
+				From:    []*Address{},
+				Sender:  []*Address{},
+				ReplyTo: []*Address{},
+				To:      []*Address{},
+				Cc:      []*Address{},
+				Bcc:     []*Address{},
 			},
-			BodyStructure: &common.BodyStructure{
+			BodyStructure: &BodyStructure{
 				Params: map[string]string{},
 			},
 		},
@@ -459,17 +457,17 @@ var bodyStructureTests = []struct{
 	{
 		fields: []interface{}{"application", "pdf", []interface{}{}, nil, nil, "base64", "4242",
 			"e0323a9039add2978bf5b49550572c7c", "attachment", []interface{}{"en-US"}, []interface{}{}},
-		bodyStructure: &common.BodyStructure{
-			MimeType: "application",
+		bodyStructure: &BodyStructure{
+			MimeType:    "application",
 			MimeSubType: "pdf",
-			Params: map[string]string{},
-			Encoding: "base64",
-			Size: 4242,
-			Extended: true,
-			Md5: "e0323a9039add2978bf5b49550572c7c",
+			Params:      map[string]string{},
+			Encoding:    "base64",
+			Size:        4242,
+			Extended:    true,
+			Md5:         "e0323a9039add2978bf5b49550572c7c",
 			Disposition: "attachment",
-			Language: []string{"en-US"},
-			Location: []string{},
+			Language:    []string{"en-US"},
+			Location:    []string{},
 		},
 	},
 	{
@@ -478,26 +476,26 @@ var bodyStructureTests = []struct{
 			[]interface{}{"text", "html", []interface{}{}, nil, nil, "us-ascii", "106", "36"},
 			"alternative",
 		},
-		bodyStructure: &common.BodyStructure{
-			MimeType: "multipart",
+		bodyStructure: &BodyStructure{
+			MimeType:    "multipart",
 			MimeSubType: "alternative",
-			Params: map[string]string{},
-			Parts: []*common.BodyStructure{
-				&common.BodyStructure{
-					MimeType: "text",
+			Params:      map[string]string{},
+			Parts: []*BodyStructure{
+				&BodyStructure{
+					MimeType:    "text",
 					MimeSubType: "plain",
-					Params: map[string]string{},
-					Encoding: "us-ascii",
-					Size: 87,
-					Lines: 22,
+					Params:      map[string]string{},
+					Encoding:    "us-ascii",
+					Size:        87,
+					Lines:       22,
 				},
-				&common.BodyStructure{
-					MimeType: "text",
+				&BodyStructure{
+					MimeType:    "text",
 					MimeSubType: "html",
-					Params: map[string]string{},
-					Encoding: "us-ascii",
-					Size: 106,
-					Lines: 36,
+					Params:      map[string]string{},
+					Encoding:    "us-ascii",
+					Size:        106,
+					Lines:       36,
 				},
 			},
 		},
@@ -507,31 +505,31 @@ var bodyStructureTests = []struct{
 			[]interface{}{"text", "plain", []interface{}{}, nil, nil, "us-ascii", "87", "22"},
 			"alternative", []interface{}{"hello", "world"}, "inline", []interface{}{"en-US"}, []interface{}{},
 		},
-		bodyStructure: &common.BodyStructure{
-			MimeType: "multipart",
+		bodyStructure: &BodyStructure{
+			MimeType:    "multipart",
 			MimeSubType: "alternative",
-			Params: map[string]string{"hello": "world"},
-			Parts: []*common.BodyStructure{
-				&common.BodyStructure{
-					MimeType: "text",
+			Params:      map[string]string{"hello": "world"},
+			Parts: []*BodyStructure{
+				&BodyStructure{
+					MimeType:    "text",
 					MimeSubType: "plain",
-					Params: map[string]string{},
-					Encoding: "us-ascii",
-					Size: 87,
-					Lines: 22,
+					Params:      map[string]string{},
+					Encoding:    "us-ascii",
+					Size:        87,
+					Lines:       22,
 				},
 			},
-			Extended: true,
+			Extended:    true,
 			Disposition: "inline",
-			Language: []string{"en-US"},
-			Location: []string{},
+			Language:    []string{"en-US"},
+			Location:    []string{},
 		},
 	},
 }
 
 func TestBodyStructure_Parse(t *testing.T) {
 	for i, test := range bodyStructureTests {
-		bs := &common.BodyStructure{}
+		bs := &BodyStructure{}
 
 		if err := bs.Parse(test.fields); err != nil {
 			t.Errorf("Cannot parse #%v: %v", i, err)
diff --git a/common/read_test.go b/read_test.go
similarity index 95%
rename from common/read_test.go
rename to read_test.go
index 8733db8..5f32b44 100644
--- a/common/read_test.go
+++ b/read_test.go
@@ -1,4 +1,4 @@
-package common_test
+package imap_test
 
 import (
 	"bytes"
@@ -6,13 +6,13 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 func TestParseNumber(t *testing.T) {
-	tests := []struct{
-		f interface{}
-		n uint32
+	tests := []struct {
+		f   interface{}
+		n   uint32
 		err bool
 	}{
 		{f: "42", n: 42},
@@ -20,11 +20,11 @@ func TestParseNumber(t *testing.T) {
 		{f: "-1", err: true},
 		{f: "1.2", err: true},
 		{f: nil, err: true},
-		{f: common.NewLiteral([]byte("cc")), err: true},
+		{f: imap.NewLiteral([]byte("cc")), err: true},
 	}
 
 	for _, test := range tests {
-		n, err := common.ParseNumber(test.f)
+		n, err := imap.ParseNumber(test.f)
 		if err != nil {
 			if !test.err {
 				t.Errorf("Cannot parse number %v", test.f)
@@ -40,34 +40,34 @@ func TestParseNumber(t *testing.T) {
 }
 
 func TestParseStringList(t *testing.T) {
-	tests := []struct{
+	tests := []struct {
 		fields []interface{}
-		list []string
+		list   []string
 	}{
 		{
 			fields: []interface{}{"a", "b", "c", "d"},
-			list: []string{"a", "b", "c", "d"},
+			list:   []string{"a", "b", "c", "d"},
 		},
 		{
 			fields: []interface{}{"a"},
-			list: []string{"a"},
+			list:   []string{"a"},
 		},
 		{
 			fields: []interface{}{},
-			list: []string{},
+			list:   []string{},
 		},
 		{
 			fields: []interface{}{"a", 42, "c", "d"},
-			list: nil,
+			list:   nil,
 		},
 		{
 			fields: []interface{}{"a", nil, "c", "d"},
-			list: nil,
+			list:   nil,
 		},
 	}
 
 	for _, test := range tests {
-		list, err := common.ParseStringList(test.fields)
+		list, err := imap.ParseStringList(test.fields)
 		if err != nil {
 			if test.list != nil {
 				t.Errorf("Cannot parse string list: %v", err)
@@ -78,9 +78,9 @@ func TestParseStringList(t *testing.T) {
 	}
 }
 
-func newReader(s string) (b *bytes.Buffer, r *common.Reader) {
+func newReader(s string) (b *bytes.Buffer, r *imap.Reader) {
 	b = bytes.NewBuffer([]byte(s))
-	r = common.NewReader(b)
+	r = imap.NewReader(b)
 	return
 }
 
@@ -326,7 +326,7 @@ func TestReader_ReadList(t *testing.T) {
 		t.Error("Field 1 has not the expected value:", fields[0])
 	} else if s, ok := fields[1].(string); !ok || s != "field2" {
 		t.Error("Field 2 has not the expected value:", fields[1])
-	} else if literal, ok := fields[2].(*common.Literal); !ok || literal.String() != "field3" {
+	} else if literal, ok := fields[2].(*imap.Literal); !ok || literal.String() != "field3" {
 		t.Error("Field 3 has not the expected value:", fields[2])
 	} else if s, ok := fields[3].(string); !ok || s != "field4" {
 		t.Error("Field 4 has not the expected value:", fields[3])
diff --git a/common/response_test.go b/response_test.go
similarity index 73%
rename from common/response_test.go
rename to response_test.go
index c589976..b522233 100644
--- a/common/response_test.go
+++ b/response_test.go
@@ -1,17 +1,17 @@
-package common_test
+package imap_test
 
 import (
 	"bytes"
 	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 func TestResp_WriteTo(t *testing.T) {
 	var b bytes.Buffer
-	w := common.NewWriter(&b)
+	w := imap.NewWriter(&b)
 
-	resp := &common.Resp{
+	resp := &imap.Resp{
 		Fields: []interface{}{"76", "FETCH", []interface{}{"UID", 783}},
 	}
 
@@ -26,9 +26,9 @@ func TestResp_WriteTo(t *testing.T) {
 
 func TestContinuationResp_WriteTo(t *testing.T) {
 	var b bytes.Buffer
-	w := common.NewWriter(&b)
+	w := imap.NewWriter(&b)
 
-	resp := &common.ContinuationResp{}
+	resp := &imap.ContinuationResp{}
 
 	if err := resp.WriteTo(w); err != nil {
 		t.Fatal(err)
@@ -41,9 +41,9 @@ func TestContinuationResp_WriteTo(t *testing.T) {
 
 func TestContinuationResp_WriteTo_WithInfo(t *testing.T) {
 	var b bytes.Buffer
-	w := common.NewWriter(&b)
+	w := imap.NewWriter(&b)
 
-	resp := &common.ContinuationResp{Info: "send literal"}
+	resp := &imap.ContinuationResp{Info: "send literal"}
 
 	if err := resp.WriteTo(w); err != nil {
 		t.Fatal(err)
@@ -56,14 +56,14 @@ func TestContinuationResp_WriteTo_WithInfo(t *testing.T) {
 
 func TestReadResp_ContinuationResp(t *testing.T) {
 	b := bytes.NewBufferString("+ send literal\r\n")
-	r := common.NewReader(b)
+	r := imap.NewReader(b)
 
-	resp, err := common.ReadResp(r)
+	resp, err := imap.ReadResp(r)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	cont, ok := resp.(*common.ContinuationResp)
+	cont, ok := resp.(*imap.ContinuationResp)
 	if !ok {
 		t.Fatal("Response is not a continuation request")
 	}
@@ -75,14 +75,14 @@ func TestReadResp_ContinuationResp(t *testing.T) {
 
 func TestReadResp_ContinuationResp_NoInfo(t *testing.T) {
 	b := bytes.NewBufferString("+\r\n")
-	r := common.NewReader(b)
+	r := imap.NewReader(b)
 
-	resp, err := common.ReadResp(r)
+	resp, err := imap.ReadResp(r)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	cont, ok := resp.(*common.ContinuationResp)
+	cont, ok := resp.(*imap.ContinuationResp)
 	if !ok {
 		t.Fatal("Response is not a continuation request")
 	}
@@ -94,14 +94,14 @@ func TestReadResp_ContinuationResp_NoInfo(t *testing.T) {
 
 func TestReadResp_Resp(t *testing.T) {
 	b := bytes.NewBufferString("* 1 EXISTS\r\n")
-	r := common.NewReader(b)
+	r := imap.NewReader(b)
 
-	respi, err := common.ReadResp(r)
+	respi, err := imap.ReadResp(r)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	resp, ok := respi.(*common.Resp)
+	resp, ok := respi.(*imap.Resp)
 	if !ok {
 		t.Fatal("Invalid response type")
 	}
@@ -116,14 +116,14 @@ func TestReadResp_Resp(t *testing.T) {
 
 func TestReadResp_Resp_NoArgs(t *testing.T) {
 	b := bytes.NewBufferString("* SEARCH\r\n")
-	r := common.NewReader(b)
+	r := imap.NewReader(b)
 
-	respi, err := common.ReadResp(r)
+	respi, err := imap.ReadResp(r)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	resp, ok := respi.(*common.Resp)
+	resp, ok := respi.(*imap.Resp)
 	if !ok {
 		t.Fatal("Invalid response type")
 	}
@@ -137,65 +137,65 @@ func TestReadResp_Resp_NoArgs(t *testing.T) {
 }
 
 func TestReadResp_StatusResp(t *testing.T) {
-	tests := []struct{
-		input string
-		expected *common.StatusResp
+	tests := []struct {
+		input    string
+		expected *imap.StatusResp
 	}{
 		{
 			input: "* OK IMAP4rev1 Service Ready\r\n",
-			expected: &common.StatusResp{
-				Tag: "*",
-				Type: common.StatusOk,
+			expected: &imap.StatusResp{
+				Tag:  "*",
+				Type: imap.StatusOk,
 				Info: "IMAP4rev1 Service Ready",
 			},
 		},
 		{
 			input: "* PREAUTH Welcome Pauline!\r\n",
-			expected: &common.StatusResp{
-				Tag: "*",
-				Type: common.StatusPreauth,
+			expected: &imap.StatusResp{
+				Tag:  "*",
+				Type: imap.StatusPreauth,
 				Info: "Welcome Pauline!",
 			},
 		},
 		{
 			input: "a001 OK NOOP completed\r\n",
-			expected: &common.StatusResp{
-				Tag: "a001",
-				Type: common.StatusOk,
+			expected: &imap.StatusResp{
+				Tag:  "a001",
+				Type: imap.StatusOk,
 				Info: "NOOP completed",
 			},
 		},
 		{
 			input: "a001 OK [READ-ONLY] SELECT completed\r\n",
-			expected: &common.StatusResp{
-				Tag: "a001",
-				Type: common.StatusOk,
+			expected: &imap.StatusResp{
+				Tag:  "a001",
+				Type: imap.StatusOk,
 				Code: "READ-ONLY",
 				Info: "SELECT completed",
 			},
 		},
 		{
 			input: "a001 OK [CAPABILITY IMAP4rev1 UIDPLUS] LOGIN completed\r\n",
-			expected: &common.StatusResp{
-				Tag: "a001",
-				Type: common.StatusOk,
-				Code: "CAPABILITY",
+			expected: &imap.StatusResp{
+				Tag:       "a001",
+				Type:      imap.StatusOk,
+				Code:      "CAPABILITY",
 				Arguments: []interface{}{"IMAP4rev1", "UIDPLUS"},
-				Info: "LOGIN completed",
+				Info:      "LOGIN completed",
 			},
 		},
 	}
 
 	for _, test := range tests {
 		b := bytes.NewBufferString(test.input)
-		r := common.NewReader(b)
+		r := imap.NewReader(b)
 
-		resp, err := common.ReadResp(r)
+		resp, err := imap.ReadResp(r)
 		if err != nil {
 			t.Fatal(err)
 		}
 
-		status, ok := resp.(*common.StatusResp)
+		status, ok := resp.(*imap.StatusResp)
 		if !ok {
 			t.Fatal("Response is not a status:", resp)
 		}
diff --git a/common/search_test.go b/search_test.go
similarity index 61%
rename from common/search_test.go
rename to search_test.go
index a60d6de..3381ead 100644
--- a/common/search_test.go
+++ b/search_test.go
@@ -1,23 +1,21 @@
-package common_test
+package imap
 
 import (
 	"reflect"
 	"testing"
 	"time"
-
-	"github.com/emersion/go-imap/common"
 )
 
 // Note to myself: writing these broing tests actually fixed 2 bugs :P
 
-var searchSeqSet1, _ = common.NewSeqSet("1:42")
-var searchSeqSet2, _ = common.NewSeqSet("743:938")
+var searchSeqSet1, _ = NewSeqSet("1:42")
+var searchSeqSet2, _ = NewSeqSet("743:938")
 var searchDate1 = time.Date(1997, 11, 21, 0, 0, 0, 0, time.UTC)
 var searchDate2 = time.Date(1984, 11, 5, 0, 0, 0, 0, time.UTC)
 
-var searchCriteriaTests = []struct{
-	fields []interface{}
-	criteria *common.SearchCriteria
+var searchCriteriaTests = []struct {
+	fields   []interface{}
+	criteria *SearchCriteria
 }{
 	{
 		fields: []interface{}{
@@ -51,39 +49,39 @@ var searchCriteriaTests = []struct{
 			"UNKEYWORD", "microsoft",
 			"UNSEEN",
 		},
-		criteria: &common.SearchCriteria{
-			SeqSet: searchSeqSet1,
+		criteria: &SearchCriteria{
+			SeqSet:   searchSeqSet1,
 			Answered: true,
-			Bcc: "root@nsa.gov",
-			Before: &searchDate1,
-			Body: "hey there",
-			Cc: "root@gchq.gov.uk",
-			Deleted: true,
-			Draft: true,
-			Flagged: true,
-			From: "root@protonmail.com",
-			Header: [2]string{"Content-Type", "text/csv"},
-			Keyword: "cc",
-			Larger: 4242,
-			New: true,
-			Not: &common.SearchCriteria{Old: true, On: &searchDate2},
-			Or: [2]*common.SearchCriteria{
-				&common.SearchCriteria{Recent: true, SentOn: &searchDate1},
-				&common.SearchCriteria{Seen: true, SentBefore: &searchDate2},
+			Bcc:      "root@nsa.gov",
+			Before:   searchDate1,
+			Body:     "hey there",
+			Cc:       "root@gchq.gov.uk",
+			Deleted:  true,
+			Draft:    true,
+			Flagged:  true,
+			From:     "root@protonmail.com",
+			Header:   [2]string{"Content-Type", "text/csv"},
+			Keyword:  "cc",
+			Larger:   4242,
+			New:      true,
+			Not:      &SearchCriteria{Old: true, On: searchDate2},
+			Or: [2]*SearchCriteria{
+				&SearchCriteria{Recent: true, SentOn: searchDate1},
+				&SearchCriteria{Seen: true, SentBefore: searchDate2},
 			},
-			SentSince: &searchDate1,
-			Since: &searchDate2,
-			Smaller: 643,
-			Subject: "saucisse royale",
-			Text: "DILLE",
-			To: "cc@dille.cc",
-			Uid: searchSeqSet2,
+			SentSince:  searchDate1,
+			Since:      searchDate2,
+			Smaller:    643,
+			Subject:    "saucisse royale",
+			Text:       "DILLE",
+			To:         "cc@dille.cc",
+			Uid:        searchSeqSet2,
 			Unanswered: true,
-			Undeleted: true,
-			Undraft: true,
-			Unflagged: true,
-			Unkeyword: "microsoft",
-			Unseen: true,
+			Undeleted:  true,
+			Undraft:    true,
+			Unflagged:  true,
+			Unkeyword:  "microsoft",
+			Unseen:     true,
 		},
 	},
 }
@@ -103,7 +101,7 @@ func TestSearchCriteria_Format(t *testing.T) {
 
 func TestSearchCriteria_Parse(t *testing.T) {
 	for i, test := range searchCriteriaTests {
-		criteria := &common.SearchCriteria{}
+		criteria := &SearchCriteria{}
 
 		if err := criteria.Parse(test.fields); err != nil {
 			t.Errorf("Cannot parse search criteria for #%v: %v", i, err)
diff --git a/common/seqset_test.go b/seqset_test.go
similarity index 99%
rename from common/seqset_test.go
rename to seqset_test.go
index fe2820d..a7122fb 100644
--- a/common/seqset_test.go
+++ b/seqset_test.go
@@ -1,4 +1,4 @@
-package common
+package imap
 
 import (
 	"math/rand"
diff --git a/server/cmd_any_test.go b/server/cmd_any_test.go
index f2ab6fd..0e81424 100644
--- a/server/cmd_any_test.go
+++ b/server/cmd_any_test.go
@@ -7,7 +7,9 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/server"
+	"github.com/emersion/go-sasl"
 )
 
 func testServerGreeted(t *testing.T) (s *server.Server, c net.Conn, scanner *bufio.Scanner) {
@@ -66,3 +68,63 @@ func TestLogout(t *testing.T) {
 		t.Fatal("Bad status response:", scanner.Text())
 	}
 }
+
+type xnoop struct{}
+
+func (ext *xnoop) Capabilities(imap.ConnState) []string {
+	return []string{"XNOOP"}
+}
+
+func (ext *xnoop) Command(string) server.HandlerFactory {
+	return nil
+}
+
+func TestServer_Enable(t *testing.T) {
+	s, c, scanner := testServerGreeted(t)
+	defer c.Close()
+	defer s.Close()
+
+	s.Enable(&xnoop{})
+
+	io.WriteString(c, "a001 CAPABILITY\r\n")
+
+	scanner.Scan()
+	if scanner.Text() != "* CAPABILITY IMAP4rev1 XNOOP AUTH=PLAIN" {
+		t.Fatal("Bad capability:", scanner.Text())
+	}
+
+	scanner.Scan()
+	if !strings.HasPrefix(scanner.Text(), "a001 OK ") {
+		t.Fatal("Bad status response:", scanner.Text())
+	}
+}
+
+type xnoopAuth struct{}
+
+func (ext *xnoopAuth) Next(response []byte) (challenge []byte, done bool, err error) {
+	done = true
+	return
+}
+
+func TestServer_EnableAuth(t *testing.T) {
+	s, c, scanner := testServerGreeted(t)
+	defer c.Close()
+	defer s.Close()
+
+	s.EnableAuth("XNOOP", func(server.Conn) sasl.Server {
+		return &xnoopAuth{}
+	})
+
+	io.WriteString(c, "a001 CAPABILITY\r\n")
+
+	scanner.Scan()
+	if scanner.Text() != "* CAPABILITY IMAP4rev1 AUTH=PLAIN AUTH=XNOOP" &&
+		scanner.Text() != "* CAPABILITY IMAP4rev1 AUTH=XNOOP AUTH=PLAIN" {
+		t.Fatal("Bad capability:", scanner.Text())
+	}
+
+	scanner.Scan()
+	if !strings.HasPrefix(scanner.Text(), "a001 OK ") {
+		t.Fatal("Bad status response:", scanner.Text())
+	}
+}
diff --git a/server/cmd_auth_test.go b/server/cmd_auth_test.go
index 23b62a1..c64aacc 100644
--- a/server/cmd_auth_test.go
+++ b/server/cmd_auth_test.go
@@ -26,14 +26,14 @@ func TestSelect_Ok(t *testing.T) {
 	io.WriteString(c, "a001 SELECT INBOX\r\n")
 
 	got := map[string]bool{
-		"OK": false,
-		"FLAGS": false,
-		"EXISTS": false,
-		"RECENT": false,
-		"UNSEEN": false,
+		"OK":             false,
+		"FLAGS":          false,
+		"EXISTS":         false,
+		"RECENT":         false,
+		"UNSEEN":         false,
 		"PERMANENTFLAGS": false,
-		"UIDNEXT": false,
-		"UIDVALIDITY": false,
+		"UIDNEXT":        false,
+		"UIDVALIDITY":    false,
 	}
 
 	for scanner.Scan() {
diff --git a/server/cmd_noauth_test.go b/server/cmd_noauth_test.go
index 45630af..9b0004b 100644
--- a/server/cmd_noauth_test.go
+++ b/server/cmd_noauth_test.go
@@ -1,9 +1,9 @@
 package server_test
 
 import (
-	"io"
 	"bufio"
 	"crypto/tls"
+	"io"
 	"strings"
 	"testing"
 
@@ -22,7 +22,7 @@ func TestStartTLS(t *testing.T) {
 
 	tlsConfig := &tls.Config{
 		InsecureSkipVerify: true,
-		Certificates: []tls.Certificate{cert},
+		Certificates:       []tls.Certificate{cert},
 	}
 
 	s.AllowInsecureAuth = false
diff --git a/common/status_test.go b/status_test.go
similarity index 64%
rename from common/status_test.go
rename to status_test.go
index 915d5f9..ce38245 100644
--- a/common/status_test.go
+++ b/status_test.go
@@ -1,56 +1,56 @@
-package common_test
+package imap_test
 
 import (
 	"bytes"
 	"testing"
 
-	"github.com/emersion/go-imap/common"
+	"github.com/emersion/go-imap"
 )
 
 func TestStatusResp_WriteTo(t *testing.T) {
-	tests := []struct{
-		input *common.StatusResp
+	tests := []struct {
+		input    *imap.StatusResp
 		expected string
 	}{
 		{
-			input: &common.StatusResp{
-				Tag: "*",
-				Type: common.StatusOk,
+			input: &imap.StatusResp{
+				Tag:  "*",
+				Type: imap.StatusOk,
 			},
 			expected: "* OK \r\n",
 		},
 		{
-			input: &common.StatusResp{
-				Tag: "*",
-				Type: common.StatusOk,
+			input: &imap.StatusResp{
+				Tag:  "*",
+				Type: imap.StatusOk,
 				Info: "LOGIN completed",
 			},
 			expected: "* OK LOGIN completed\r\n",
 		},
 		{
-			input: &common.StatusResp{
-				Tag: "42",
-				Type: common.StatusBad,
+			input: &imap.StatusResp{
+				Tag:  "42",
+				Type: imap.StatusBad,
 				Info: "Invalid arguments",
 			},
 			expected: "42 BAD Invalid arguments\r\n",
 		},
 		{
-			input: &common.StatusResp{
-				Tag: "a001",
-				Type: common.StatusOk,
+			input: &imap.StatusResp{
+				Tag:  "a001",
+				Type: imap.StatusOk,
 				Code: "READ-ONLY",
 				Info: "EXAMINE completed",
 			},
 			expected: "a001 OK [READ-ONLY] EXAMINE completed\r\n",
 		},
 		{
-			input: &common.StatusResp{
-				Tag: "*",
-				Type: common.StatusOk,
-				Code: "CAPABILITY",
+			input: &imap.StatusResp{
+				Tag:       "*",
+				Type:      imap.StatusOk,
+				Code:      "CAPABILITY",
 				Arguments: []interface{}{"IMAP4rev1"},
-				Info: "IMAP4rev1 service ready",
+				Info:      "IMAP4rev1 service ready",
 			},
 			expected: "* OK [CAPABILITY IMAP4rev1] IMAP4rev1 service ready\r\n",
 		},
@@ -58,7 +58,7 @@ func TestStatusResp_WriteTo(t *testing.T) {
 
 	for i, test := range tests {
 		b := &bytes.Buffer{}
-		w := common.NewWriter(b)
+		w := imap.NewWriter(b)
 
 		if err := test.input.WriteTo(w); err != nil {
 			t.Errorf("Cannot write status #%v, got error: %v", i, err)
@@ -73,19 +73,19 @@ func TestStatusResp_WriteTo(t *testing.T) {
 }
 
 func TestStatus_Err(t *testing.T) {
-	status := &common.StatusResp{Type: common.StatusOk, Info: "All green"}
+	status := &imap.StatusResp{Type: imap.StatusOk, Info: "All green"}
 	if err := status.Err(); err != nil {
 		t.Error("OK status returned error:", err)
 	}
 
-	status = &common.StatusResp{Type: common.StatusBad, Info: "BAD!"}
+	status = &imap.StatusResp{Type: imap.StatusBad, Info: "BAD!"}
 	if err := status.Err(); err == nil {
 		t.Error("BAD status didn't returned error:", err)
 	} else if err.Error() != "BAD!" {
 		t.Error("BAD status returned incorrect error message:", err)
 	}
 
-	status = &common.StatusResp{Type: common.StatusNo, Info: "NO!"}
+	status = &imap.StatusResp{Type: imap.StatusNo, Info: "NO!"}
 	if err := status.Err(); err == nil {
 		t.Error("NO status didn't returned error:", err)
 	} else if err.Error() != "NO!" {
diff --git a/common/write_test.go b/write_test.go
similarity index 57%
rename from common/write_test.go
rename to write_test.go
index 9e695fd..1912f82 100644
--- a/common/write_test.go
+++ b/write_test.go
@@ -1,34 +1,21 @@
-package common_test
+package imap
 
 import (
 	"bytes"
 	"testing"
 	"time"
-
-	"github.com/emersion/go-imap/common"
 )
 
-func newWriter() (w *common.Writer, b *bytes.Buffer) {
+func newWriter() (w *writer, b *bytes.Buffer) {
 	b = &bytes.Buffer{}
-	w = common.NewWriter(b)
+	w = NewWriter(b).writer()
 	return
 }
 
-func TestWriter_WriteSp(t *testing.T) {
-	w, b := newWriter()
-
-	if _, err := w.WriteSp(); err != nil {
-		t.Error(err)
-	}
-	if b.String() != " " {
-		t.Error("Not a separator")
-	}
-}
-
 func TestWriter_WriteCrlf(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteCrlf(); err != nil {
+	if err := w.writeCrlf(); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\r\n" {
@@ -36,10 +23,10 @@ func TestWriter_WriteCrlf(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteNil(t *testing.T) {
+func TestWriter_WriteField_Nil(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteNil(); err != nil {
+	if err := w.writeField(nil); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "NIL" {
@@ -47,10 +34,10 @@ func TestWriter_WriteNil(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteNumber(t *testing.T) {
+func TestWriter_WriteField_Number(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteNumber(42); err != nil {
+	if err := w.writeField(uint32(42)); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "42" {
@@ -58,10 +45,10 @@ func TestWriter_WriteNumber(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteString_Atom(t *testing.T) {
+func TestWriter_WriteField_Atom(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("BODY[]"); err != nil {
+	if err := w.writeField("BODY[]"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "BODY[]" {
@@ -72,7 +59,7 @@ func TestWriter_WriteString_Atom(t *testing.T) {
 func TestWriter_WriteString_Quoted(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("I love potatoes!"); err != nil {
+	if err := w.writeField("I love potatoes!"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"I love potatoes!\"" {
@@ -83,7 +70,7 @@ func TestWriter_WriteString_Quoted(t *testing.T) {
 func TestWriter_WriteString_Quoted_WithSpecials(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("I love \"1984\"!"); err != nil {
+	if err := w.writeField("I love \"1984\"!"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"I love \\\"1984\\\"!\"" {
@@ -91,10 +78,21 @@ func TestWriter_WriteString_Quoted_WithSpecials(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteString_8bit(t *testing.T) {
+func TestWriter_WriteField_ForcedQuoted(t *testing.T) {
+	w, b := newWriter()
+
+	if err := w.writeField(Quoted("dille")); err != nil {
+		t.Error(err)
+	}
+	if b.String() != "\"dille\"" {
+		t.Error("Not the expected atom:", b.String())
+	}
+}
+
+func TestWriter_WriteField_8bitString(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("☺"); err != nil {
+	if err := w.writeField("☺"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "{3}\r\n☺" {
@@ -102,10 +100,10 @@ func TestWriter_WriteString_8bit(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteString_Nil(t *testing.T) {
+func TestWriter_WriteField_NilString(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("NIL"); err != nil {
+	if err := w.writeField("NIL"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"NIL\"" {
@@ -113,10 +111,10 @@ func TestWriter_WriteString_Nil(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteString_Empty(t *testing.T) {
+func TestWriter_WriteField_EmptyString(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString(""); err != nil {
+	if err := w.writeField(""); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"\"" {
@@ -124,11 +122,11 @@ func TestWriter_WriteString_Empty(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteDate(t *testing.T) {
+func TestWriter_WriteField_DateTime(t *testing.T) {
 	w, b := newWriter()
 
-	date := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
-	if _, err := w.WriteDate(&date); err != nil {
+	dt := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+	if err := w.writeField(dt); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"10-Nov-2009 23:00:00 +0000\"" {
@@ -136,10 +134,11 @@ func TestWriter_WriteDate(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteDate_Nil(t *testing.T) {
+func TestWriter_WriteField_ZeroDateTime(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteDate(nil); err != nil {
+	dt := time.Time{}
+	if err := w.writeField(dt); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "NIL" {
@@ -150,7 +149,7 @@ func TestWriter_WriteDate_Nil(t *testing.T) {
 func TestWriter_WriteFields(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteFields([]interface{}{"hey", 42}); err != nil {
+	if err := w.writeFields([]interface{}{"hey", 42}); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "hey 42" {
@@ -158,10 +157,10 @@ func TestWriter_WriteFields(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteList_Simple(t *testing.T) {
+func TestWriter_WriteField_SimpleList(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteList([]interface{}{"hey", 42}); err != nil {
+	if err := w.writeField([]interface{}{"hey", 42}); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "(hey 42)" {
@@ -169,7 +168,7 @@ func TestWriter_WriteList_Simple(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteList_Nested(t *testing.T) {
+func TestWriter_WriteField_NestedList(t *testing.T) {
 	w, b := newWriter()
 
 	list := []interface{}{
@@ -181,7 +180,7 @@ func TestWriter_WriteList_Nested(t *testing.T) {
 		22,
 	}
 
-	if _, err := w.WriteList(list); err != nil {
+	if err := w.writeField(list); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "(toplevel (nested 0) 22)" {
@@ -189,12 +188,12 @@ func TestWriter_WriteList_Nested(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteLiteral(t *testing.T) {
+func TestWriter_WriteField_Literal(t *testing.T) {
 	w, b := newWriter()
 
-	literal := common.NewLiteral([]byte("hello world"))
+	literal := NewLiteral([]byte("hello world"))
 
-	if _, err := w.WriteLiteral(literal); err != nil {
+	if err := w.writeField(literal); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "{11}\r\nhello world" {
@@ -205,7 +204,7 @@ func TestWriter_WriteLiteral(t *testing.T) {
 func TestWriter_WriteRespCode_NoArgs(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteRespCode("READ-ONLY", nil); err != nil {
+	if err := w.writeRespCode("READ-ONLY", nil); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "[READ-ONLY]" {
@@ -217,7 +216,7 @@ func TestWriter_WriteRespCode_WithArgs(t *testing.T) {
 	w, b := newWriter()
 
 	args := []interface{}{"IMAP4rev1", "STARTTLS", "LOGINDISABLED"}
-	if _, err := w.WriteRespCode("CAPABILITY", args); err != nil {
+	if err := w.writeRespCode("CAPABILITY", args); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "[CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED]" {
@@ -225,13 +224,13 @@ func TestWriter_WriteRespCode_WithArgs(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteInfo(t *testing.T) {
+func TestWriter_WriteLine(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteInfo("NOOP completed"); err != nil {
+	if err := w.writeLine("*", "OK"); err != nil {
 		t.Error(err)
 	}
-	if b.String() != "NOOP completed\r\n" {
-		t.Error("Not the expected info")
+	if b.String() != "* OK\r\n" {
+		t.Error("Not the expected line:", b.String())
 	}
 }