Skip to content

📤 A SMTP/LMTP server library written in Go

License

Notifications You must be signed in to change notification settings

mschneider82/go-smtp

 
 

Repository files navigation

go-smtp

GoDoc codecov

This is a forked version of go-smtp, changed API to function options. Added NewDefaultBackend and DefaultSession for easier usage. Client has a own package. Added XForward support

Features

  • ESMTP client & server implementing RFC 5321
  • Support for SMTP AUTH and PIPELINING
  • UTF-8 support for subject and message
  • LMTP support
  • XFORWARD support
  • Since v1.1.2: Keep \r\n in Data Reader (textproto DotReader replaces it to \n)

SMTP Server

package main

import (
	"io"
	"io/ioutil"
	"log"
	"time"

	"github.com/mschneider82/go-smtp"
)

// A Session is returned after successful login.
type Session struct{
	smtp.DefaultSession
}


func (s *Session) Data(r io.Reader, d smtp.DataContext) error {
	if b, err := ioutil.ReadAll(r); err != nil {
		return err
	} else {
		log.Println("Data:", string(b))
	}
	return nil
}

type sessionFactory struct{}

func (s *sessionFactory) New() smtp.Session {
	return &Session{}
}

func main() {
	err := smtp.NewServer(
		smtp.NewDefaultBackend(&sessionFactory{}),
		smtp.Addr(":1025"),
		smtp.Domain("localhost"),
		smtp.WriteTimeout(10*time.Second),
		smtp.ReadTimeout(10*time.Second),
		smtp.MaxMessageBytes(1024*1024),
		smtp.MaxRecipients(50),
		smtp.AllowInsecureAuth(),
		smtp.DisableAuth(),
	).ListenAndServe()

	if err != nil {
		log.Fatal(err)
	}
}

You can use the server manually with telnet:

$ telnet localhost 1025
EHLO localhost
AUTH PLAIN
AHVzZXJuYW1lAHBhc3N3b3Jk
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
DATA
Hey <3
.

LMTP Server

package main

import (
	"context"
	"io"
	"io/ioutil"
	"log"
	"time"
	"fmt"

	"github.com/mschneider82/go-smtp"
)

// Session is returned for every connection
type Session struct{
	smtp.DefaultSession
}

func (s *Session) Data(r io.Reader, dataContext smtp.DataContext) error {
	mailBytes, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	} 
	
	globalCtx := context.Background()

	for i, rcpt := range s.Rcpts {
		rcptCtx, _ := context.WithTimeout(globalCtx, 2*time.Second)
		// we have to assing i and rcpt new to access them in the go() routine
		rcpt := rcpt
		i := i

		dataContext.StartDelivery(rcptCtx, rcpt)
		go func() {
			// normaly we would deliver the mailBytes to our Maildir/HTTP backend
			// in this case we just do a sleep 
			time.Sleep(time.Duration(2+i) * time.Second)
			fmt.Println(string(mailBytes))
            // Lets finish with OK (if the request wasn't canceled because of the ctx timeout)
			dataContext.SetStatus(rcpt, &smtp.SMTPError{
				Code: 250,
				EnhancedCode:  smtp.EnhancedCode{2, 0, 0},
				Message: "Finished",
			})
		}()

	}
	// we always return nil in LMTP because every rcpt return code was set with dataContext.SetStatus()
	return nil
}

func main() {
	err := smtp.NewServer(
		smtp.NewDefaultBackend(&Session{}),
		smtp.Addr(":1025"),
		smtp.Domain("localhost"),
		smtp.WriteTimeout(10*time.Second),
		smtp.ReadTimeout(10*time.Second),
		smtp.MaxMessageBytes(1024*1024),
		smtp.MaxRecipients(50),
		smtp.AllowInsecureAuth(),
		smtp.DisableAuth(),
		smtp.LMTP(),
	).ListenAndServe()

	if err != nil {
		log.Fatal(err)
	}
}

You can use the server manually with telnet:

$ telnet localhost 1025
LHLO localhost
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
RCPT TO:<[email protected]>
RCPT TO:<[email protected]>
DATA
Hey <3
.
250 2.0.0 <[email protected]> Finished
420 4.4.7 <[email protected]> Error: timeout reached
420 4.4.7 <[email protected]> Error: timeout reached

Licence

MIT

About

📤 A SMTP/LMTP server library written in Go

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Go 100.0%