Simple Mail Transfer Protocol 의 약자
SMTP 의 자세한 내용은 Network/SMTP 에서 확인
net/smtp
Package SMTP는 RFC 5321 의 SMTP, RFC 1652 의 8BITMIME, RFC 2554 의 AUTH, RFC 3207 의 STARTTLS 를 구현해 놓은 내장 패키지다.
이메일 발송을 위해 사용한다.
SMTP 서버와 연결된 클라이언트.
type Client struct {
Text *textproto.Conn
conn net.Conn
tls bool
serverName string
ext map[string]string
auth []string
localName string
didHello bool
helloError error
}
- Text
Client 가 extensions 를 추가할 수 있게 해준다. - conn
이후 TLS 연결을 위해 연결에 관한 기록을 보유한다.
net.Conn 인터페이스의 메소드는 아래와 같다.- Read
- Write
- Close
- LocalAddr
- RemoteAddr
- SetDeadline
- SetReadDeadline
- SetWriteDeadline
- tls
tls를 사용하는가의 여부 - serverName
서버명 - ext
서포트하는 익스텐션 (map 타입) - auth
서포트하는 auth mechanisms - localName
HELO/EHLO 에서 사용할 이름 - didHello
- helloError
hello 로부터 파생된 에러
연결 끊기
func (c *Client) Close() error {
return c.Text.Close()
}
서버가 지원하는 extension인지 보고해준다.
- extension 명은 case-insensitive
func (c *Client) Extension(ext string) (bool, string) {
if err := c.hello(); err != nil {
return false, ""
}
if c.ext == nil {
return false, ""
}
ext = strings.ToUpper(ext)
param, ok := c.ext[ext]
return ok, param
}
func (c *Client) Hello(localName string) error {
if err := validateLine(localName); err != nil {
return err
}
if c.didHello {
return errors.New("smtp: Hello called after other methods")
}
c.localName = localName
return c.hello()
}
- HELO
Hello
해당 명령어를 통해 서버를 식별한 후 SMTP 대화를 시작한다.
RSET 명령어를 서버에 보냄으로써 현재 진행중인 메일 transaction 을 중단시킨다.
func (c *Client) Reset() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "RSET")
return err
}
- RSET
Reset
전체 트랜잭션을 무효화 한 후 버퍼를 다시 설정(Reset)시키는 명령어.
NOOP 명령어를 서버에 보낸다.
이를 통해 서버가 연결이 문제 없는지, 클라이언트와 통신가능한지 확인한다.
func (c *Client) Noop() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "NOOP")
return err
}
- NOOP
No Operation
수신자가 OK 응답을 보내도록 한다.
QUIT 명령어를 서버에 보낸다.
func (c *Client) Quit() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(221, "QUIT")
if err != nil {
return err
}
return c.Text.Close()
}
- QUIT
서버의 연결을 닫음
서버는 해당 명령어를 받게 되면 221 코드를 보낸후 세션을 닫는다.
SMTP의 라인 규격을 맞추었는지 확인한다.
func validateLine(line string) error {
if strings.ContainsAny(line, "\n\r") {
return errors.New("smtp: A line must not contain CR or LF")
}
return nil
}
- 매 라인은 RFC 5321 규격에 근거, CR(\r) + LF(\n) 로 끝나야 한다.
- CR : Carriage Return
- LF : Line Feed
메일 전송
func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
if err := validateLine(from); err != nil {
return err
}
for _, recp := range to {
if err := validateLine(recp); err != nil {
return err
}
}
c, err := Dial(addr)
if err != nil {
return err
}
defer c.Close()
if err = c.hello(); err != nil {
return err
}
if ok, _ := c.Extension("STARTTLS"); ok {
config := &tls.Config{ServerName: c.serverName}
if testHookStartTLS != nil {
testHookStartTLS(config)
}
if err = c.StartTLS(config); err != nil {
return err
}
}
if a != nil && c.ext != nil {
if _, ok := c.ext["AUTH"]; !ok {
return errors.New("smtp: server doesn't support AUTH")
}
if err = c.Auth(a); err != nil {
return err
}
}
if err = c.Mail(from); err != nil {
return err
}
for _, addr := range to {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = w.Write(msg)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()
}
- Parameters
- addr
SMTP 서버주소, Port 번호 - a
Auth 인터페이스 - from
송신자 이메일 주소 - to
수신자 이메일 주소 - msg
이메일에 담길 내용
RFC 822 의 형식을 따라야 한다.- Email Header
- Blank Line
메일의 헤더와 바디를 구분하기위해 Blank line이 필요 - Message Body
매 라인은 CRLF 로 끝나야함
- addr