func (c *Conn) Uidl(msgID int) ([]MessageID, error) {
	var (
		buf *bytes.Buffer
		err error
	)
	if msgID <= 0 {
		buf, err = c.Cmd("UIDL", true)
	} else {
		buf, err = c.Cmd("UIDL", false, msgID)
	}
	if err != nil {
		return nil, err
	}
	var (
		out   []MessageID
		lines = bytes.Split(buf.Bytes(), lineBreak)
	)
	for _, l := range lines {
		f := bytes.Fields(l)
		if len(f) == 0 {
			break
		}
		id, err := strconv.Atoi(string(f[0]))
		if err != nil {
			return nil, err
		}
		out = append(out, MessageID{ID: id, UID: string(f[1])})
	}
	return out, nil
}
Uidl returns a list of (message ID, message UID) pairs. If the optional msgID
is > 0, then only that particular message is listed. It works like Top() but only works on
servers that support the UIDL command. Messages size field is not available in the UIDL response.
package pop3
import (
	"bufio"
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"
	"github.com/emersion/go-message"
)
type Client struct {
	opt Opt
}
type Conn struct {
	conn net.Conn
	r    *bufio.Reader
	w    *bufio.Writer
}
type Opt struct {
	Host string `json:"host"`
	Port int    `json:"port"`
	DialTimeout time.Duration `json:"dial_timeout"`
	TLSEnabled    bool `json:"tls_enabled"`
	TLSSkipVerify bool `json:"tls_skip_verify"`
}
type MessageID struct {
	ID   int
	Size int
	UID string
}
var (
	lineBreak = []byte("\r\n")
	respOK      = []byte("+OK")    
	respOKInfo  = []byte("+OK ")   
	respErr     = []byte("-ERR")   
	respErrInfo = []byte("-ERR ")  
)
func New(opt Opt) *Client {
	if opt.DialTimeout < time.Millisecond {
		opt.DialTimeout = time.Second * 3
	}
	return &Client{
		opt: opt,
	}
}
func (c *Client) NewConn() (*Conn, error) {
	var (
		addr = fmt.Sprintf("%s:%d", c.opt.Host, c.opt.Port)
	)
	conn, err := net.DialTimeout("tcp", addr, c.opt.DialTimeout)
	if err != nil {
		return nil, err
	}
	if c.opt.TLSEnabled {
		tlsCfg := tls.Config{}
		if c.opt.TLSSkipVerify {
			tlsCfg.InsecureSkipVerify = c.opt.TLSSkipVerify
		} else {
			tlsCfg.ServerName = c.opt.Host
		}
		conn = tls.Client(conn, &tlsCfg)
	}
	pCon := &Conn{
		conn: conn,
		r:    bufio.NewReader(conn),
		w:    bufio.NewWriter(conn),
	}
	if _, err := pCon.ReadOne(); err != nil {
		return nil, err
	}
	return pCon, nil
}
func (c *Conn) Send(b string) error {
	if _, err := c.w.WriteString(b + "\r\n"); err != nil {
		return err
	}
	return c.w.Flush()
}
func (c *Conn) Cmd(cmd string, isMulti bool, args ...interface{}) (*bytes.Buffer, error) {
	var cmdLine string
	if len(args) > 0 {
		format := " " + strings.TrimRight(strings.Repeat("%v ", len(args)), " ")
		cmdLine = fmt.Sprintf(cmd+format, args...)
	} else {
		cmdLine = cmd
	}
	if err := c.Send(cmdLine); err != nil {
		return nil, err
	}
	b, err := c.ReadOne()
	if err != nil {
		return nil, err
	}
	if !isMulti {
		return bytes.NewBuffer(b), err
	}
	buf, err := c.ReadAll()
	return buf, err
}
func (c *Conn) ReadOne() ([]byte, error) {
	b, _, err := c.r.ReadLine()
	if err != nil {
		return nil, err
	}
	r, err := parseResp(b)
	return r, err
}
func (c *Conn) ReadAll() (*bytes.Buffer, error) {
	buf := &bytes.Buffer{}
	for {
		b, _, err := c.r.ReadLine()
		if err != nil {
			return nil, err
		}
		if bytes.Equal(b, []byte(".")) {
			break
		}
		if _, err := buf.Write(b); err != nil {
			return nil, err
		}
		if _, err := buf.Write(lineBreak); err != nil {
			return nil, err
		}
	}
	return buf, nil
}
func (c *Conn) Auth(user, password string) error {
	if err := c.User(user); err != nil {
		return err
	}
	if err := c.Pass(password); err != nil {
		return err
	}
	return c.Noop()
}
func (c *Conn) User(s string) error {
	_, err := c.Cmd("USER", false, s)
	return err
}
func (c *Conn) Pass(s string) error {
	_, err := c.Cmd("PASS", false, s)
	return err
}
func (c *Conn) Stat() (int, int, error) {
	b, err := c.Cmd("STAT", false)
	if err != nil {
		return 0, 0, err
	}
	f := bytes.Fields(b.Bytes())
	count, err := strconv.Atoi(string(f[0]))
	if err != nil {
		return 0, 0, err
	}
	if count == 0 {
		return 0, 0, nil
	}
	size, err := strconv.Atoi(string(f[1]))
	if err != nil {
		return 0, 0, err
	}
	return count, size, nil
}
func (c *Conn) List(msgID int) ([]MessageID, error) {
	var (
		buf *bytes.Buffer
		err error
	)
	if msgID <= 0 {
		buf, err = c.Cmd("LIST", true)
	} else {
		buf, err = c.Cmd("LIST", false, msgID)
	}
	if err != nil {
		return nil, err
	}
	var (
		out   []MessageID
		lines = bytes.Split(buf.Bytes(), lineBreak)
	)
	for _, l := range lines {
		f := bytes.Fields(l)
		if len(f) == 0 {
			break
		}
		id, err := strconv.Atoi(string(f[0]))
		if err != nil {
			return nil, err
		}
		size, err := strconv.Atoi(string(f[1]))
		if err != nil {
			return nil, err
		}
		out = append(out, MessageID{ID: id, Size: size})
	}
	return out, nil
}