Repository: jhillyerd/enmime (go)

Issue:
PR checks fail
https://github.com/jhillyerd/enmime/blob/master/.github/workflows/pull-checks.yml is trying to use go 1.17 packages on 1.13 and failing

Pull request description:
Fix linter issues
Fixes #241 

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index e92f58e..2abaec6 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -12,7 +12,6 @@ linters:
     - gosimple
     - govet
     - ineffassign
-    - lll
     - misspell
     - megacheck
     - prealloc
diff --git a/builder.go b/builder.go
index 31e414d..c08ae83 100644
--- a/builder.go
+++ b/builder.go
@@ -7,7 +7,6 @@ import (
 	"mime"
 	"net/mail"
 	"net/textproto"
-	"os"
 	"path/filepath"
 	"reflect"
 	"time"
@@ -156,12 +155,7 @@ func (p MailBuilder) AddFileAttachment(path string) MailBuilder {
 	if p.err != nil {
 		return p
 	}
-	f, err := os.Open(path)
-	if err != nil {
-		p.err = err
-		return p
-	}
-	b, err := ioutil.ReadAll(f)
+	b, err := ioutil.ReadFile(path)
 	if err != nil {
 		p.err = err
 		return p
@@ -196,19 +190,49 @@ func (p MailBuilder) AddFileInline(path string) MailBuilder {
 	if p.err != nil {
 		return p
 	}
-	f, err := os.Open(path)
+	b, err := ioutil.ReadFile(path)
 	if err != nil {
 		p.err = err
 		return p
 	}
-	b, err := ioutil.ReadAll(f)
+	name := filepath.Base(path)
+	ctype := mime.TypeByExtension(filepath.Ext(name))
+	return p.AddInline(b, ctype, name, name)
+}
+
+// AddOtherPart returns a copy of MailBuilder that includes the specified embedded part.
+// fileName may be left empty.
+// It's useful when you want to embed image with CID.
+func (p MailBuilder) AddOtherPart(
+	b []byte,
+	contentType string,
+	fileName string,
+	contentID string,
+) MailBuilder {
+	part := NewPart(contentType)
+	part.Content = b
+	part.FileName = fileName
+	part.ContentID = contentID
+	p.inlines = append(p.inlines, part)
+	return p
+}
+
+// AddFileOtherPart returns a copy of MailBuilder that includes the specified other part.
+// Filename and contentID will be populated from the base name of path.
+// Content type will be detected from the path extension.
+func (p MailBuilder) AddFileOtherPart(path string) MailBuilder {
+	// Only allow first p.err value
+	if p.err != nil {
+		return p
+	}
+	b, err := ioutil.ReadFile(path)
 	if err != nil {
 		p.err = err
 		return p
 	}
 	name := filepath.Base(path)
 	ctype := mime.TypeByExtension(filepath.Ext(name))
-	return p.AddInline(b, ctype, name, name)
+	return p.AddOtherPart(b, ctype, name, name)
 }
 
 // Build performs some basic validations, then constructs a tree of Part structs from the configured
@@ -232,6 +256,7 @@ func (p MailBuilder) Build() (*Part, error) {
 	//  |  |- multipart/alternative
 	//  |  |  |- text/plain
 	//  |  |  `- text/html
+	//  |  |- other parts..
 	//  |  `- inlines..
 	//  `- attachments..
 	//
@@ -263,7 +288,7 @@ func (p MailBuilder) Build() (*Part, error) {
 		root = NewPart(ctMultipartRelated)
 		root.AddChild(part)
 		for _, ip := range p.inlines {
-			// Copy inline Part to isolate mutations
+			// Copy inline/other part to isolate mutations
 			part = &Part{}
 			*part = *ip
 			part.Header = make(textproto.MIMEHeader)
diff --git a/encode.go b/encode.go
index 9449399..fab9075 100644
--- a/encode.go
+++ b/encode.go
@@ -36,9 +36,13 @@ func (p *Part) Encode(writer io.Writer) error {
 	cte := p.setupMIMEHeaders()
 	// Encode this part.
 	b := bufio.NewWriter(writer)
-	p.encodeHeader(b)
+	if err := p.encodeHeader(b); err != nil {
+		return err
+	}
 	if len(p.Content) > 0 {
-		b.Write(crnl)
+		if _, err := b.Write(crnl); err != nil {
+			return err
+		}
 		if err := p.encodeContent(b, cte); err != nil {
 			return err
 		}
@@ -51,15 +55,23 @@ func (p *Part) Encode(writer io.Writer) error {
 	marker := endMarker[:len(endMarker)-2]
 	c := p.FirstChild
 	for c != nil {
-		b.Write(marker)
-		b.Write(crnl)
+		if _, err := b.Write(marker); err != nil {
+			return err
+		}
+		if _, err := b.Write(crnl); err != nil {
+			return err
+		}
 		if err := c.Encode(b); err != nil {
 			return err
 		}
 		c = c.NextSibling
 	}
-	b.Write(endMarker)
-	b.Write(crnl)
+	if _, err := b.Write(endMarker); err != nil {
+		return err
+	}
+	if _, err := b.Write(crnl); err != nil {
+		return err
+	}
 	return b.Flush()
 }
 
@@ -134,7 +146,7 @@ func (p *Part) setupMIMEHeaders() transferEncoding {
 }
 
 // encodeHeader writes out a sorted list of headers.
-func (p *Part) encodeHeader(b *bufio.Writer) {
+func (p *Part) encodeHeader(b *bufio.Writer) error {
 	keys := make([]string, 0, len(p.Header))
 	for k := range p.Header {
 		keys = append(keys, k)
@@ -152,9 +164,12 @@ func (p *Part) encodeHeader(b *bufio.Writer) {
 			// _ used to prevent early wrapping
 			wb := stringutil.Wrap(76, k, ":_", encv, "\r\n")
 			wb[len(k)+1] = ' '
-			b.Write(wb)
+			if _, err := b.Write(wb); err != nil {
+				return err
+			}
 		}
 	}
+	return nil
 }
 
 // encodeContent writes out the content in the selected encoding.
@@ -173,7 +188,9 @@ func (p *Part) encodeContent(b *bufio.Writer, cte transferEncoding) (err error)
 			if _, err = b.Write(text[:lineLen]); err != nil {
 				return err
 			}
-			b.Write(crnl)
+			if _, err := b.Write(crnl); err != nil {
+				return err
+			}
 			text = text[lineLen:]
 		}
 	case teQuoted:
diff --git a/envelope.go b/envelope.go
index 4303590..01ac872 100644
--- a/envelope.go
+++ b/envelope.go
@@ -57,7 +57,7 @@ func (e *Envelope) GetHeaderValues(name string) []string {
 	}
 
 	rawValues := (*e.header)[textproto.CanonicalMIMEHeaderKey(name)]
-	var values []string
+	values := make([]string, 0, len(rawValues))
 	for _, v := range rawValues {
 		values = append(values, coding.DecodeExtHeader(v))
 	}
diff --git a/go.mod b/go.mod
index c0f08e1..cecb125 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
+	github.com/stretchr/testify v1.7.0
 	golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0 // indirect
 	golang.org/x/text v0.3.6
 )
diff --git a/go.sum b/go.sum
index bb6cf33..6bbbdbd 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
 github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
 github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
 github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
 github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 h1:gBeyun7mySAKWg7Fb0GOcv0upX9bdaZScs8QcRo8mEY=
@@ -13,12 +15,16 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
 github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0 h1:6QqBc2UURz4Sbr4IE15uXM8CTQlHnRdtKuogDhwnu2Y=
 golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -27,3 +33,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/header.go b/header.go
index 52591ed..633fc8a 100644
--- a/header.go
+++ b/header.go
@@ -21,13 +21,11 @@ const (
 	cdInline     = "inline"
 
 	// Standard MIME content types
-	ctAppPrefix        = "application/"
 	ctAppOctetStream   = "application/octet-stream"
 	ctMultipartAltern  = "multipart/alternative"
 	ctMultipartMixed   = "multipart/mixed"
 	ctMultipartPrefix  = "multipart/"
 	ctMultipartRelated = "multipart/related"
-	ctTextPrefix       = "text/"
 	ctTextPlain        = "text/plain"
 	ctTextHTML         = "text/html"
 
@@ -93,8 +91,9 @@ func ParseAddressList(list string) ([]*mail.Address, error) {
 	}
 
 	for i := range ret {
-		// try to additionally decode Name with less strict decoder
+		// try to additionally decode with less strict decoder
 		ret[i].Name = coding.DecodeExtHeader(ret[i].Name)
+		ret[i].Address = coding.DecodeExtHeader(ret[i].Address)
 	}
 
 	return ret, nil
diff --git a/mediatype/mediatype.go b/mediatype/mediatype.go
index 7ad75f4..cc0164f 100644
--- a/mediatype/mediatype.go
+++ b/mediatype/mediatype.go
@@ -13,15 +13,12 @@ import (
 
 const (
 	// Standard MIME content types
-	ctAppPrefix        = "application/"
-	ctAppOctetStream   = "application/octet-stream"
-	ctMultipartAltern  = "multipart/alternative"
-	ctMultipartMixed   = "multipart/mixed"
-	ctMultipartPrefix  = "multipart/"
-	ctMultipartRelated = "multipart/related"
-	ctTextPrefix       = "text/"
-	ctTextPlain        = "text/plain"
-	ctTextHTML         = "text/html"
+	ctAppPrefix       = "application/"
+	ctAppOctetStream  = "application/octet-stream"
+	ctMultipartMixed  = "multipart/mixed"
+	ctMultipartPrefix = "multipart/"
+	ctTextPrefix      = "text/"
+	ctTextPlain       = "text/plain"
 
 	// Used as a placeholder in case of malformed Content-Type headers
 	ctPlaceholder = "x-not-a-mime-type/x-not-a-mime-type"
@@ -256,7 +253,7 @@ findValueStart:
 
 		_, runeLength := _utf8.DecodeRuneInString(s[i:])
 		s = s[i+runeLength:]
-		escaped := false
+		escaped := r == '\\'
 
 	findValueEnd:
 		for i, r = range s {


Test patch:
diff --git a/builder_test.go b/builder_test.go
index 6732a5d..437b0c2 100644
--- a/builder_test.go
+++ b/builder_test.go
@@ -6,11 +6,14 @@ import (
 	"path/filepath"
 	"reflect"
 	"strconv"
+	"strings"
 	"testing"
 	"time"
 
 	"github.com/jhillyerd/enmime"
 	"github.com/jhillyerd/enmime/internal/test"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 type mockSender struct {
@@ -790,6 +793,127 @@ func TestBuilderAddFileInline(t *testing.T) {
 	}
 }
 
+func TestBuilderAddOtherPartSame(t *testing.T) {
+	a := enmime.Builder().AddOtherPart([]byte("same"), "ct", "fn", "cid")
+	b := enmime.Builder().AddOtherPart([]byte("same"), "ct", "fn", "cid")
+	assert.Equal(t, a, b)
+}
+
+func TestBuilderAddOtherPartNotSame(t *testing.T) {
+	a := enmime.Builder().AddOtherPart([]byte("foo"), "ct", "fn", "cid")
+	b := enmime.Builder().AddOtherPart([]byte("bar"), "ct", "fn", "cid")
+	assert.NotEqual(t, a, b)
+}
+
+func TestBuilderAddOtherPart(t *testing.T) {
+	a := enmime.Builder().AddOtherPart([]byte("foo"), "ct", "fn", "cid")
+	b := a.AddOtherPart([]byte("bar"), "ct", "fn", "cid")
+	b1 := b.AddOtherPart([]byte("baz"), "ct", "fn", "cid")
+	b2 := b.AddOtherPart([]byte("bax"), "ct", "fn", "cid")
+	assert.NotEqual(t, a, b, "AddOtherPart() should not mutate receiver")
+	assert.NotEqual(t, b, b1, "AddOtherPart() should not mutate receiver")
+	assert.NotEqual(t, b1, b2, "AddOtherPart() should not mutate receiver")
+
+	want := "fake JPG data"
+	name := "photo.jpg"
+	cid := "<mycid>"
+	contentType := "image/jpeg"
+	a = enmime.Builder().
+		Text([]byte("text")).
+		HTML([]byte("html")).
+		From("name", "foo").
+		Subject("foo").
+		ToAddrs(addrSlice).
+		AddOtherPart([]byte(want), contentType, name, cid)
+	root, err := a.Build()
+	require.NoError(t, err)
+
+	buf := bytes.Buffer{}
+	require.NoError(t, root.Encode(&buf))
+	e, err := enmime.ReadEnvelope(bytes.NewReader(buf.Bytes()))
+	require.NoError(t, err)
+	require.Len(t, e.OtherParts, 1)
+	assert.Equal(t, name, e.OtherParts[0].FileName)
+	assert.Equal(t, contentType, e.OtherParts[0].ContentType)
+	assert.Equal(t, cid, e.OtherParts[0].ContentID)
+	assert.Equal(t, want, string(e.OtherParts[0].Content))
+
+	p := root.DepthMatchFirst(func(p *enmime.Part) bool { return p.ContentID == cid })
+	require.NotNil(t, p)
+	assert.Equal(t, "", p.Disposition)
+	assert.Equal(t, want, string(p.Content))
+
+	// Check structure
+	wantTypes := []string{
+		"multipart/related",
+		"multipart/alternative",
+		"text/plain",
+		"text/html",
+		"image/jpeg",
+	}
+	gotParts := root.DepthMatchAll(func(p *enmime.Part) bool { return true })
+	gotTypes := make([]string, 0)
+	for _, p := range gotParts {
+		contentType := p.ContentType
+		// remove second part because it mostly random
+		if strings.Contains(p.ContentType, ";") {
+			contentType = strings.Split(p.ContentType, ";")[0]
+		}
+		gotTypes = append(gotTypes, contentType)
+	}
+	test.DiffStrings(t, gotTypes, wantTypes)
+}
+
+func TestBuilderAddFileOtherPart(t *testing.T) {
+	a := enmime.Builder().AddFileOtherPart("zzzDOESNOTEXIST")
+	err := a.Error()
+	require.Error(t, err)
+	_, gotErr := a.Build()
+	assert.Equal(t, err, gotErr)
+	b := a.AddFileOtherPart("zzzDOESNOTEXIST2")
+	assert.Equal(t, err, b.Error())
+
+	a = enmime.Builder().From("name", "from")
+	_ = a.AddFileOtherPart("zzzDOESNOTEXIST")
+	assert.NoError(t, a.Error(), "AddFileOtherPart error mutated receiver")
+
+	a = enmime.Builder().AddFileOtherPart(filepath.Join("testdata", "attach", "fake.png"))
+	require.NoError(t, a.Error())
+	b = enmime.Builder().AddFileOtherPart(filepath.Join("testdata", "attach", "fake.png"))
+	require.NoError(t, b.Error())
+	assert.Equal(t, a, b)
+
+	a = enmime.Builder().AddFileOtherPart(filepath.Join("testdata", "attach", "fake.png"))
+	require.NoError(t, a.Error())
+	b = enmime.Builder().AddFileOtherPart(filepath.Join("testdata", "mail", "attachment.raw"))
+	require.NoError(t, b.Error())
+	assert.NotEqual(t, a, b)
+
+	a = enmime.Builder().AddFileOtherPart(filepath.Join("testdata", "attach", "fake.png"))
+	b = a.AddFileOtherPart(filepath.Join("testdata", "attach", "fake.png"))
+	b1 := b.AddFileOtherPart(filepath.Join("testdata", "attach", "fake.png"))
+	b2 := b.AddFileOtherPart(filepath.Join("testdata", "mail", "attachment.raw"))
+	assert.NotEqual(t, a, b, "AddFileOtherPart() should not mutate receiver, failed")
+	assert.NotEqual(t, b, b1, "AddFileOtherPart() should not mutate receiver, failed")
+	assert.NotEqual(t, b1, b2, "AddFileOtherPart() should not mutate receiver, failed")
+
+	name := "fake.png"
+	ctype := "image/png"
+	a = enmime.Builder().
+		Text([]byte("text")).
+		HTML([]byte("html")).
+		From("name", "foo").
+		Subject("foo").
+		ToAddrs(addrSlice).
+		AddFileOtherPart(filepath.Join("testdata", "attach", "fake.png"))
+	root, err := a.Build()
+	require.NoError(t, err)
+	p := root.DepthMatchFirst(func(p *enmime.Part) bool { return p.ContentID == name })
+	require.NotNil(t, p)
+	p = root.DepthMatchFirst(func(p *enmime.Part) bool { return p.ContentType == ctype })
+	require.NotNil(t, p)
+}
+
 func TestValidation(t *testing.T) {
 	_, err := enmime.Builder().
 		To("name", "address").
@@ -921,7 +1045,7 @@ func TestBuilderQPHeaders(t *testing.T) {
 		t.Fatal(err)
 	}
 	b := &bytes.Buffer{}
-	p.Encode(b)
+	err = p.Encode(b)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/envelope_test.go b/envelope_test.go
index 8d9d4f5..8ad5185 100644
--- a/envelope_test.go
+++ b/envelope_test.go
@@ -683,7 +683,10 @@ func TestEnvelopeSetHeader(t *testing.T) {
 
 	// replace existing header
 	want := "André Pirard <PIRARD@vm1.ulg.ac.be>"
-	e.SetHeader("To", []string{want})
+	err = e.SetHeader("To", []string{want})
+	if err != nil {
+		t.Fatal(err)
+	}
 	got := e.GetHeader("To")
 	if got != want {
 		t.Errorf("Got: %q, want: %q", got, want)
@@ -691,7 +694,10 @@ func TestEnvelopeSetHeader(t *testing.T) {
 
 	// replace existing header with multiple values
 	wantSlice := []string{"Mirosław Marczak <marczak@inbucket.com>", "James Hillyerd <jamehi03@jamehi03lx.noa.com>"}
-	e.SetHeader("To", wantSlice)
+	err = e.SetHeader("To", wantSlice)
+	if err != nil {
+		t.Fatal(err)
+	}
 	gotSlice := e.GetHeaderValues("to")
 	diff := deep.Equal(gotSlice, wantSlice)
 	if diff != nil {
@@ -700,7 +706,10 @@ func TestEnvelopeSetHeader(t *testing.T) {
 
 	// replace non-existing header
 	want = "foobar"
-	e.SetHeader("X-Foo-Bar", []string{want})
+	err = e.SetHeader("X-Foo-Bar", []string{want})
+	if err != nil {
+		t.Fatal(err)
+	}
 	got = e.GetHeader("X-Foo-Bar")
 	if got != want {
 		t.Errorf("Got: %q, want: %q", got, want)
@@ -717,7 +726,10 @@ func TestEnvelopeAddHeader(t *testing.T) {
 	// add to existing header
 	to := "James Hillyerd <jamehi03@jamehi03lx.noa.com>"
 	wantSlice := []string{"\"Mirosław Marczak\" <marczak@inbucket.com>", "James Hillyerd <jamehi03@jamehi03lx.noa.com>"}
-	e.AddHeader("To", to)
+	err = e.AddHeader("To", to)
+	if err != nil {
+		t.Fatal(err)
+	}
 	gotSlice := e.GetHeaderValues("To")
 	diff := deep.Equal(gotSlice, wantSlice)
 	if diff != nil {
@@ -726,7 +738,10 @@ func TestEnvelopeAddHeader(t *testing.T) {
 
 	// add to non-existing header
 	want := "foobar"
-	e.AddHeader("X-Foo-Bar", want)
+	err = e.AddHeader("X-Foo-Bar", want)
+	if err != nil {
+		t.Fatal(err)
+	}
 	got := e.GetHeader("X-Foo-Bar")
 	if got != want {
 		t.Errorf("Got: %q, want: %q", got, want)
@@ -741,7 +756,10 @@ func TestEnvelopeDeleteHeader(t *testing.T) {
 	}
 
 	// delete user-agent header
-	e.DeleteHeader("User-Agent")
+	err = e.DeleteHeader("User-Agent")
+	if err != nil {
+		t.Fatal(err)
+	}
 	got := e.GetHeader("User-Agent")
 	want := ""
 	if got != want {
@@ -803,6 +821,12 @@ Message body
 				{Name: "", Address: "other@company.com"},
 			},
 		},
+		{
+			`=?UTF-8?B?dGVzdG5hbWU=?= <=?UTF-8?B?dGVzdG5hbWU=?=@example.com>`,
+			[]*mail.Address{
+				{Name: "testname", Address: "testname@example.com"},
+			},
+		},
 	}
 	for _, tc := range testCases {
 		t.Run(tc.input, func(t *testing.T) {
diff --git a/example_test.go b/example_test.go
index 0b797a7..3266a5b 100644
--- a/example_test.go
+++ b/example_test.go
@@ -28,10 +28,16 @@ func ExampleBuilder() {
 
 	// master is immutable, causing each msg below to have a single recipient.
 	msg := master.To("Esteemed Customer", "user1@inbucket.org")
-	msg.Send(sender)
+	err := msg.Send(sender)
+	if err != nil {
+		panic(err)
+	}
 
 	msg = master.To("Another Customer", "user2@inbucket.org")
-	msg.Send(sender)
+	err = msg.Send(sender)
+	if err != nil {
+		panic(err)
+	}
 }
 
 func ExampleReadEnvelope() {
diff --git a/header_test.go b/header_test.go
index e31234b..48d6926 100644
--- a/header_test.go
+++ b/header_test.go
@@ -115,6 +115,15 @@ func TestParseAddressListResult(t *testing.T) {
 				},
 			},
 		},
+		{
+			`=?UTF-8?B?dGVzdG5hbWU=?= <=?UTF-8?B?dGVzdG5hbWU=?=@example.com>`,
+			[]*mail.Address{
+				{
+					Name:    "testname",
+					Address: "testname@example.com",
+				},
+			},
+		},
 	}
 	for _, tc := range testCases {
 		t.Run(tc.input, func(t *testing.T) {
diff --git a/mediatype/mediatype_test.go b/mediatype/mediatype_test.go
index e221b1f..95093b7 100644
--- a/mediatype/mediatype_test.go
+++ b/mediatype/mediatype_test.go
@@ -303,6 +303,11 @@ func TestFixUnquotedSpecials(t *testing.T) {
 			input: `text/plain; name=Untitled document.txt; disposition=inline`,
 			want:  `text/plain; name="Untitled document.txt"; disposition=inline`,
 		},
+		{
+			// Escaped character at the beginning of param.
+			input: `application/pdf; name=\"pdf\"pdf\"`,
+			want:  `application/pdf; name="\"pdf\"pdf\""`,
+		},
 	}
 	for _, tc := range testCases {
 		t.Run(tc.input, func(t *testing.T) {