10

I'm writing a package which I intend to make one initial connection to the local SMTP server and then it waits on a channel which will be populated with emails to send when they are needed to be sent.

Looking at net/http it appears to be expecting that the SMTP server should be dialed into and authenticated each time an email is to be sent. Surely I can dial and authenticate once, keep the connection open and just add new emails as they come?

Looking at the source for smtp.SendMail, I don't see how this can be done, as there does not appear to be a way to recycle the *Client: http://golang.org/src/net/smtp/smtp.go?s=7610:7688#L263

There is a Reset function for *Client, but the description for that is:

 Reset sends the RSET command to the server, aborting the current mail transaction.

I don't want to abort the current mail transaction, I want to have multiple mail transactions.

How can I keep the connection to the SMTP server open and send multiple emails on that one connection?

Alasdair
  • 13,348
  • 18
  • 82
  • 138

2 Answers2

10

You are correct that smtp.SendMail does not provide a way to reuse the connection.

If you want that fine grained control, you should use a persistent client connection. This will require knowing a bit more about the smtp commands and protocol.

  1. Use smtp.Dial to open a connection and create a client object.
  2. Use client.Hello, client.Auth, and client.StartTLS as appropriate.
  3. Use Rcpt, Mail, Data as approprate to send messages.
  4. client.Quit() and close connection when you are all done.
captncraig
  • 22,118
  • 17
  • 108
  • 151
  • Thanks... at what point does the connection reset for sending another email? Do I use `client.Quit()` at the end of each message or at the end of sending all messages? – Alasdair May 26 '15 at 06:22
  • quit means "I am done and ready to disconnect." Only send it at the end of all messages. – captncraig May 26 '15 at 14:48
  • 2
    No need for step one (opening a `net.Conn` yourself). You can just use `smtp.Dial`. Basically, just do almost exactly what `smtp.SendMail` does except that before calling `Quit` loop back and do more `Mail`, `Rcpt`, and `Data` calls. – Dave C May 26 '15 at 16:48
  • In this case then, I will never run Quit() or Close() since the Go webapp remains dialed into the SMTP server at all times. Will the connection automatically terminate when the Go app exits? – Alasdair May 27 '15 at 06:55
  • It worked! Thanks a lot. I put the Quit and Close as deferred in the goroutine that waits on the channels anyway. So when the goroutine is terminated then it will close the connection. – Alasdair May 27 '15 at 07:04
  • @DaveC I think you are not correct. In case something goes wrong within any call, not calling `Quit` and `Dial` again will leave you with errors like on every message after the faulty one (in case of an error happening after first `Mail` call, in `Rcpt` or `Data`, every next message will fail on `Mail` call with error `503 Only one sender per message, please` for example). – Dmitry Verhoturov Dec 11 '19 at 08:20
3

Gomail v2 now supports sending multiple emails in one connection. The Daemon example from the documentation seems to match your use case:

ch := make(chan *gomail.Message)

go func() {
    d := gomail.NewPlainDialer("smtp.example.com", 587, "user", "123456")

    var s gomail.SendCloser
    var err error
    open := false
    for {
        select {
        case m, ok := <-ch:
            if !ok {
                return
            }
            if !open {
                if s, err = d.Dial(); err != nil {
                    panic(err)
                }
                open = true
            }
            if err := gomail.Send(s, m); err != nil {
                log.Print(err)
            }
        // Close the connection to the SMTP server if no email was sent in
        // the last 30 seconds.
        case <-time.After(30 * time.Second):
            if open {
                if err := s.Close(); err != nil {
                    panic(err)
                }
                open = false
            }
        }
    }
}()

// Use the channel in your program to send emails.

// Close the channel to stop the mail daemon.
close(ch)
Ale
  • 1,914
  • 17
  • 25