Skip to content

Refactorings #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ go:
- "1.8.x"
- "1.9.x"
- "1.10.x"

before_install:
- go get -u gopkg.in/alecthomas/gometalinter.v2
- gometalinter.v2 --install

script:
- gometalinter.v2 --disable=gas --disable=gocyclo ./...
- go test -v ./...
41 changes: 25 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
Go sendmail [![Build Status](https://travis-ci.org/meehow/sendmail.svg?branch=master)](https://travis-ci.org/meehow/sendmail)
===========
# Go sendmail

This package implements classic, well known from PHP, method of sending emails.
It's stupid simple and it works not only with Sendmail,
but also with other MTAs, like [Postfix](http://www.postfix.org/sendmail.1.html)
or [sSMTP](https://wiki.debian.org/sSMTP), which provide compatibility interface.
[![GoDoc](https://godoc.org/github.com/meehow/sendmail?status.svg)](https://godoc.org/github.com/meehow/sendmail)
[![Build Status](https://travis-ci.org/meehow/sendmail.svg?branch=master)](https://travis-ci.org/meehow/sendmail)


This package implements the classic method of sending emails, well known
from PHP. It's stupid simple and it works not only with Sendmail, but also
with other MTAs, like [Postfix][], [sSMTP][], or [mhsendmail][], which
provide a compatible interface.

[Postfix]: http://www.postfix.org/sendmail.1.html
[sSMTP]: https://wiki.debian.org/sSMTP
[mhsendmail]: https://github.com/mailhog/mhsendmail

* it separates email headers from email body,
* encodes UTF-8 headers like `Subject`, `From`, `To`
* makes it easy to use [text/template](https://golang.org/pkg/text/template)
* doesn't require any SMTP configuration,
* just uses `/usr/sbin/sendmail` command which is present on most of the systems,
* if not, just update `sendmail.Binary`
* outputs emails to _stdout_ when environment variable `DEBUG` is set.
* can write email body to a custom `io.Writer` to simplify testing
* by default, it just uses `/usr/sbin/sendmail` (but can be changed if need be)


## Installation

Installation
------------
```
go get -u github.com/meehow/sendmail
```

Usage
-----
## Usage

```go
package main

Expand Down Expand Up @@ -59,7 +66,9 @@ tpl.ExecuteTemplate(&sm.Text, "email", &struct{ Name string }{"Dominik"})
```


ToDo
----
## ToDo

* HTML emails
- [x] HTML emails
- [ ] multipart emails (HTML + Text)
- [ ] attachments
- [ ] inline attachments
104 changes: 104 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package sendmail

import (
"io"
"net/mail"
"os"
)

// SetSendmail modifies the path to the sendmail binary. You can pass
// additional arguments, if you need to.
func (m *Mail) SetSendmail(path string, args ...string) *Mail {
m.sendmailPath = path
m.sendmailArgs = args
return m
}

// SetDebug sets the debug output to stderr if active is true, else it
// removes the debug output. Use SetDebugOutput to set it to something else.
func (m *Mail) SetDebug(active bool) *Mail {
var out io.Writer
if active {
out = os.Stderr
}
m.debugOut = out
return m
}

// SetDebugOutput sets the debug output to the given writer. If w is
// nil, this is equivalent to SetDebug(false).
func (m *Mail) SetDebugOutput(w io.Writer) *Mail {
m.debugOut = w
return m
}

// AppendTo adds a recipient to the Mail.
func (m *Mail) AppendTo(toAddress ...*mail.Address) *Mail {
m.To = append(m.To, toAddress...)
return m
}

// AppendCC adds a carbon-copy recipient to the Mail.
func (m *Mail) AppendCC(ccAddress ...*mail.Address) *Mail {
m.CC = append(m.CC, ccAddress...)
return m
}

// AppendBCC adds a blind carbon-copy recipient to the Mail.
func (m *Mail) AppendBCC(bccAddress ...*mail.Address) *Mail {
m.BCC = append(m.BCC, bccAddress...)
return m
}

// SetFrom updates (replaces) the sender's address.
func (m *Mail) SetFrom(fromAddress *mail.Address) *Mail {
m.From = fromAddress
return m
}

// SetSubject sets the mail subject.
func (m *Mail) SetSubject(subject string) *Mail {
m.Subject = subject
return m
}

// Option is used in the Mail constructor.
type Option interface {
execute(*Mail)
}

type optionFunc func(*Mail)

func (o optionFunc) execute(m *Mail) { o(m) }

// Sendmail modifies the path to the sendmail binary.
func Sendmail(path string, args ...string) Option {
return optionFunc(func(m *Mail) { m.SetSendmail(path, args...) })
}

// Debug sets the debug output to stderr if active is true, else it
// removes the debug output. Use SetDebugOutput to set it to something else.
func Debug(active bool) Option {
return optionFunc(func(m *Mail) { m.SetDebug(active) })
}

// DebugOutput sets the debug output to the given writer. If w is nil,
// this is equivalent to SetDebug(false).
func DebugOutput(w io.Writer) Option {
return optionFunc(func(m *Mail) { m.SetDebugOutput(w) })
}

// To adds a recipient to the Mail.
func To(address *mail.Address) Option {
return optionFunc(func(m *Mail) { m.AppendTo(address) })
}

// From sets the sender's address.
func From(fromAddress *mail.Address) Option {
return optionFunc(func(m *Mail) { m.SetFrom(fromAddress) })
}

// Subject sets the mail subject.
func Subject(subject string) Option {
return optionFunc(func(m *Mail) { m.SetSubject(subject) })
}
120 changes: 120 additions & 0 deletions options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package sendmail

import (
"bytes"
"net/mail"
"os"
"testing"
)

func TestChaningOptions(t *testing.T) {
var buf bytes.Buffer
m := &Mail{
To: []*mail.Address{
&mail.Address{Name: "Michał", Address: "me@example.com"},
},
}
if m.Subject != "" {
t.Errorf("Expected subject to be empty, got %q", m.Subject)
}
if len(m.To) != 1 {
t.Errorf("Expected len(To) to be 1, got %d: %+v", len(m.To), m.To)
}
if m.From != nil {
t.Errorf("Expected From address to be nil, got %s", m.From)
}
if m.sendmailPath != "" {
t.Errorf("Expected initial sendmail to be empty, got %q", m.sendmailPath)
}
if m.debugOut != nil {
t.Errorf("Expected initial debugOut to be nil, got %T", m.debugOut)
}

m.SetSubject("Test subject").
SetFrom(&mail.Address{Name: "Dominik", Address: "dominik@example.org"}).
AppendTo(&mail.Address{Name: "Dominik2", Address: "dominik2@example.org"}).
SetDebugOutput(&buf).
SetSendmail("/bin/true")

if m.Subject != "Test subject" {
t.Errorf("Expected subject to be %q, got %q", "Test subject", m.Subject)
}
if len(m.To) != 2 {
t.Errorf("Expected len(To) to be 2, got %d: %+v", len(m.To), m.To)
}
if m.From == nil || m.From.Address != "dominik@example.org" {
expected := mail.Address{Name: "Dominik", Address: "dominik@example.org"}
t.Errorf("Expected From address to be %s, got %s", expected, m.From)
}
if m.sendmailPath != "/bin/true" {
t.Errorf("Expected sendmail to be %q, got %q", "/bin/true", m.sendmailPath)
}
if m.debugOut != &buf {
t.Errorf("Expected debugOut to be %T (buf), got %T", &buf, m.debugOut)
}
}

func TestOptions(t *testing.T) {
m := &Mail{}

o := Sendmail("/foo/bar", "--verbose")
if o.execute(m); m.sendmailPath != "/foo/bar" {
t.Errorf("Expected sendmail to be %q, got %q", "/foo/bar", m.sendmailPath)
}
if len(m.sendmailArgs) != 1 || m.sendmailArgs[0] != "--verbose" {
t.Errorf("Expected sendmail args to be %q, got %v", "--verbose", m.sendmailArgs)
}

o = Debug(true)
if o.execute(m); m.debugOut != os.Stderr {
t.Errorf("Expected debugOut to be %T (stderr), got %T", os.Stderr, m.debugOut)
}

o = Debug(false)
if o.execute(m); m.debugOut != nil {
t.Errorf("Expected debugOut to be nil, got %T", m.debugOut)
}

var buf bytes.Buffer
o = DebugOutput(&buf)
if o.execute(m); m.debugOut != &buf {
t.Errorf("Expected debugOut to be %T (buf), got %T", &buf, m.debugOut)
}

o = DebugOutput(nil)
if o.execute(m); m.debugOut != nil {
t.Errorf("Expected debugOut to be nil, got %T", m.debugOut)
}

// To() appends list
o = To(&mail.Address{Name: "Ktoś", Address: "info@example.com"})
if o.execute(m); len(m.To) != 1 {
t.Errorf("Expected len(To) to be 1, got %d: %+v", len(m.To), m.To)
}
o = To(&mail.Address{Name: "Ktoś2", Address: "info2@example.com"})
if o.execute(m); len(m.To) != 2 {
t.Errorf("Expected len(To) to be 2, got %d: %+v", len(m.To), m.To)
}

// From() updates current sender
o = From(&mail.Address{Name: "Michał", Address: "me@example.com"})
if o.execute(m); m.From == nil || m.From.Address != "me@example.com" {
expected := mail.Address{Name: "Michał", Address: "me@example.com"}
t.Errorf("Expected From address to be %s, got %s", expected, m.From)
}
o = From(&mail.Address{Name: "Michał", Address: "me@example.com"})
if o.execute(m); m.From == nil || m.From.Address != "me@example.com" {
expected := mail.Address{Name: "Michał", Address: "me@example.com"}
t.Errorf("Expected From address to be %s, got %s", expected, m.From)
}

// Subject() updates current subject
o = Subject("Cześć")
if o.execute(m); m.Subject != "Cześć" {
t.Errorf("Expected Subject to be %q, got %q", "Cześć", m.Subject)
}
o = Subject("Test")
if o.execute(m); m.Subject != "Test" {
t.Errorf("Expected Subject to be %q, got %q", "Test", m.Subject)
}
}
Loading