Giter Club home page Giter Club logo

go-mail's Introduction

go-mail - Easy to use, yet comprehensive library for sending mails with Go

GoDoc codecov Go Report Card Mentioned in Awesome Go #go-mail on Discord REUSE status OpenSSF Best Practices OpenSSF Scorecard buy ma a coffee

go-mail logo

The main idea of this library was to provide a simple interface to sending mails for my JS-Mailer project. It quickly evolved into a full-fledged mail library.

go-mail follows idiomatic Go style and best practice. It's only dependency is the Go Standard Library. It combines a lot of functionality from the standard library to give easy and convenient access to mail and SMTP related tasks.

Parts of this library (especially some parts of msgwriter.go) have been forked/ported from the go-mail/mail respectively go-gomail/gomail which both seems to not be maintained anymore.

The smtp package of go-mail is forked from the original Go stdlib's net/smtp and then extended by the go-mail team.

Features

Some of the features of this library:

  • Only Standard Library dependant
  • Modern, idiomatic Go
  • Sane and secure defaults
  • Explicit SSL/TLS support
  • Implicit StartTLS support with different policies
  • Makes use of contexts for a better control flow and timeout/cancelation handling
  • SMTP Auth support (LOGIN, PLAIN, CRAM-MD, XOAUTH2)
  • RFC5322 compliant mail address validation
  • Support for common mail header field generation (Message-ID, Date, Bulk-Precedence, Priority, etc.)
  • Reusing the same SMTP connection to send multiple mails
  • Support for attachments and inline embeds (from file system, io.Reader or embed.FS)
  • Support for different encodings
  • Middleware support for 3rd-party libraries to alter mail messages
  • Support sending mails via a local sendmail command
  • Support for requestng MDNs (RFC 8098) and DSNs (RFC 1891)
  • DKIM signature support via go-mail-middlware
  • Message object satisfies io.WriteTo and io.Reader interfaces
  • Support for Go's html/template and text/template (as message body, alternative part or attachment/emebed)
  • Output to file support which allows storing mail messages as e. g. .eml files to disk to open them in a MUA
  • Debug logging of SMTP traffic
  • Custom error types for delivery errors
  • Custom dial-context functions for more control over the connection (proxing, DNS hooking, etc.)

go-mail works like a programatic email client and provides lots of methods and functionalities you would consider standard in a MUA.

Documentation

We aim for good GoDoc documenation in our library which gives you a full API reference. We also provide a more in-depth documentation website at go-mail.dev

Compatibility

Go is growing fast and providing great features with every new release. While we'd love to adopt the latest Go features into our code, we realize that not everybody using this package can run the latest Go versions. Therefore we try to implement alternative solutions for Go versions that do not support these features. Yet, the work needed to maintain the separate versions is not to be underestimated. For that reason, we might retire that code at some point. We guarantee that go-mail will always support the last four releases of Go. With two Go releases per year, this gives the user a timeframe of two years to update to the next or even the latest version of Go.

Support

We have a support and general discussion channel on Discord. Find us at: #go-mail

Middleware

The goal of go-mail is to keep it free from 3rd party dependencies and only focus on things a mail library should fulfill. Yet, since version v0.2.8 we've added support for middleware on the Msg object, allowing 3rd parties to alter a given mail message to their needs without relying on go-mail to support their specific need.

To get our users started with message middleware, we've created a collection of useful middlewares. It can be found in a seperate repository: go-mail-middlware.

Examples

We provide example code in both our GoDocs as well as on our official Website (see Documentation). For a quick start into go-mail check out our Getting started guide.

Authors/Contributors

go-mail was initially authored and developed by Winni Neessen.

Big thanks to the following people, for contributing to the go-mail project (either in form of code or by reviewing code, writing documenation or helping to translate the website):

go-mail's People

Contributors

cvette avatar dependabot[bot] avatar dhia-gharsallaoui avatar drakkan avatar gegorov2030 avatar inliquid avatar james-d-elliott avatar leahoop avatar step-security-bot avatar sters avatar wneessen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-mail's Issues

No easy way to determine if e-mail was send

Is your feature request related to a problem? Please describe.

I am sending multiple e-mails in bulk. The issue is that if there is an error on Client.Send (and thus also Client.DialAndSend) there is no easy way to determine which e-mails were send (only which e-mails were not). I can inspect m.HasSendError() to know that the e-mail was not send. But I do not know if it is send. For example, Client.Send could fail on c.checkConn and never even attempt to send any e-mail.

Describe the solution you'd like

I suggest that at the beginning of Client.Send, for all messages Msg.sendError should be set to a non-nil value, something like SendError{Reason: ErrNotAttempted}. Then it would be clear if Msg.sendError is nil that e-mail was send.

Describe alternatives you've considered

I could be calling Client.Send on each message individually myself. But that then calls checkConn again and again.

Additional context

No response

Evaluate if DKIM can/should be implemented

Is your feature request related to a problem? Please describe.

DKIM might be a feature a proper mail library should support. Of course there are already DKIM implementations out there, but that would break our "only standard library" premise. We might wanna evaluate if DKIM can be easily implemented as part of this library.

Describe the solution you'd like

Solution should allow signing mails with domain key headers.

Describe alternatives you've considered

No response

Additional context

No response

Add more code examples to the test files

GoDoc has the amazing Example feature which allows embeding Code Examples directly into tests which then are rendered as Examples for pkg.go.dev. Let's add some more code examples, so people find more value in the documentation.

Bounces from multiple providers due to 501 5.5.4 Syntax error in BODY parameter

Description

Some domains we are sending to are producing bounces with : "501 5.5.4 Syntax error in BODY parameter". We've also had an instance of "550 Maximum line length exceeded (see RFC 5322 2.1.1).". The plain text message we are sending is static and I've ensured every line is less than 78 characters long. The emails we send do contain a PDF attachment. At first I thought it may be something in our HTML template so we have since switched to only sending plain text emails and are still seeing the issue. Emails to Google and lot's of other domains are not bouncing.

Relevant code (removed error handling and redacted email addresses only):

func makeValidUTF8(s string) string {
	return strings.ToValidUTF8(s, "")
}

// email sending code
m := mail.NewMsg()
fromerr := m.FromFormat("Magnolia Fabrics", `redacted`)

toerr := m.To(`redacted`)

m.SetCharset(mail.CharsetUTF8)
m.Subject(makeValidUTF8(fmt.Sprintf("Magnolia Fabrics Invoice - %s", `645211`)))
m.SetDate()
m.SetMessageID()
m.SetImportance(mail.ImportanceHigh)

pdfreader := bytes.NewBuffer(pdf)
m.AttachReader(fmt.Sprintf("%s.pdf", `645211`), pdfreader)

m.SetBodyString(mail.TypeTextPlain, makeValidUTF8(cfg.PlainTextEmailMessage))

if !cfg.SendPlainTextOnlyEmails {
    m.SetBodyString(mail.TypeTextHTML, makeValidUTF8(string(b.Bytes())))
}

c, cerr := mail.NewClient(cfg.SMTPServer,
    mail.WithSMTPAuth(mail.SMTPAuthPlain), mail.WithUsername(cfg.SMTPUser),
    mail.WithPassword(cfg.SMTPPassword), mail.WithTLSPolicy(mail.TLSMandatory))

derr := c.DialAndSend(m)

In go.mod and go.sum:

// in go.mod
github.com/wneessen/go-mail v0.2.5 // indirect

// in go.sum
github.com/wneessen/go-mail v0.2.5 h1:lVQ5Q1hYaUNU/VL9F4AOtYaBAxgmrH+6W6wwMcDpKDE=
github.com/wneessen/go-mail v0.2.5/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=

The values of the relevant variables mentioned above are read from this TOML file:

send_plain_text_only_emails = true
smtp_server    = 'email-smtp.us-east-1.amazonaws.com'
plain_text_email_message = '''Dear Customer,

Good news! Your order is on the way and invoice is attached!

You will find your tracking number on the invoice. Tracking data may take
up to 24 hours to be accessible online.

• Please remit payment at your earliest convenience unless invoice is
marked “PAID”.
• Some items may ship separately from multiple locations. Separate
invoices will be issued when applicable.
• PLEASE INSPECT UPON RECEIPT FOR PATTERN, COLOR, DEFECTS, DAMAGE FROM
SHIPPING, CORRECT YARDAGE, ETC! Once an order is cut or sewn, no returns
will be accepted for any reason, no matter the party in error. No returns
will be authorized after 30 days of invoice date. No exceptions will be
made.

Thank your for your business!

Magnolia Fabrics'''

Domains we are seeing bounces from:
yadtel.net, eplus.net, windstream.net, lawlessupholstery.com, comporium.net

Examples of the bounces:

>>> [email protected] (reading confirmation): 501 5.5.4 Syntax error in BODY parameter
Arrival-Date: Mon, 01 Aug 2022 16:26:13 -0400
Reporting-MTA: dns; mx04.nrtc.email-ash1.sync.lan

>>> [email protected] (reading confirmation): 501 5.5.4 Syntax error in BODY parameter
Reporting-MTA: dns; mx04.nrtc.email-ash1.sync.lan
Arrival-Date: Tue, 02 Aug 2022 16:59:33 -0400

Action: failed
Last-Attempt-Date: Tue, 02 Aug 2022 16:59:33 -0400
Remote-MTA: dns; 10.219.135.68
Diagnostic-Code: smtp; 501 5.5.4 Syntax error in BODY parameter
Final-Recipient: rfc822; [email protected]
Status: 5.5.4

>>> [email protected] (reading confirmation): 501 5.5.4 Syntax error in BODY parameter

>>> [email protected] (reading confirmation): 501 5.5.4 Syntax error in BODY parameter

An error occurred while trying to deliver the mail to the following recipients:
[email protected]
Reporting-MTA: dns; a77-213.smtp-out.amazonses.com

Action: failed
Final-Recipient: rfc822; [email protected]
Diagnostic-Code: smtp; 550 Maximum line length exceeded (see RFC 5322 2.1.1).
Status: 5.3.0

Example of email successfully received at a Google account -- contents copied from the "Show Original" option in Gmail:

Delivered-To: redacted
Date: Fri, 12 Aug 2022 21:37:33 +0000
Importance: high
MIME-Version: 1.0
Message-ID: redacted
Priority: 1
Subject: Magnolia Fabrics Invoice - 42
User-Agent: go-mail v0.2.4 // https://github.com/wneessen/go-mail
X-MSMail-Priority: 1
X-Mailer: go-mail v0.2.4 // https://github.com/wneessen/go-mail
X-Priority: 1
From: redacted
To: redacted
Content-Type: multipart/mixed; boundary=f5d04f41bcb3d5701fa0e855a6fdde977c003c89d2bf398c6de7abaaebde


--f5d04f41bcb3d5701fa0e855a6fdde977c003c89d2bf398c6de7abaaebde
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8

Dear Customer,

Good news! Your order is on the way and invoice is attached!

You will find your tracking number on the invoice. Tracking data may take
up to 24 hours to be accessible online.

=E2=80=A2 Please remit payment at your earliest convenience unless invoice =
is
marked =E2=80=9CPAID=E2=80=9D.
=E2=80=A2 Some items may ship separately from multiple locations. Separate
invoices will be issued when applicable.
=E2=80=A2 PLEASE INSPECT UPON RECEIPT FOR PATTERN, COLOR, DEFECTS, DAMAGE F=
ROM
SHIPPING, CORRECT YARDAGE, ETC! Once an order is cut or sewn, no returns
will be accepted for any reason, no matter the party in error. No returns
will be authorized after 30 days of invoice date. No exceptions will be
made.

Thank your for your business!

Magnolia Fabrics
--f5d04f41bcb3d5701fa0e855a6fdde977c003c89d2bf398c6de7abaaebde
Content-Disposition: attachment; filename="42.pdf"
Content-Transfer-Encoding: base64
Content-Type: application/pdf; name="42.pdf"


--f5d04f41bcb3d5701fa0e855a6fdde977c003c89d2bf398c6de7abaaebde--

To Reproduce

We're running the app on a customer's Windows server; we build and develop on Linux.

We have run go get -u github.com/wneessen/go-mail to ensure we are on latest version of lib.

The app is built with:
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o invoice-generator.exe

Expected behaviour

No bounces due to syntax error in body.

Screenshots

No response

Attempted Fixes

No response

Additional context

No response

Stucking at [220 Ready to start TLS]

Description

2023/11/20 18:23:32 DEBUG: C --> S: EHLO chekunMacBook-Pro.local
2023/11/20 18:23:33 DEBUG: C <-- S: 250 smtp.qq.com
PIPELINING
SIZE 73400320
STARTTLS
AUTH LOGIN PLAIN
AUTH=LOGIN
MAILCOMPRESS
8BITMIME
2023/11/20 18:23:33 DEBUG: C --> S: STARTTLS
2023/11/20 18:23:33 DEBUG: C <-- S: 220 Ready to start TLS
2023/11/20 18:23:33 DEBUG: C --> S: EHLO chekunMacBook-Pro.local

To Reproduce

use dummy account and password to connect to smtp.exmail.qq.com:587 and send email

Expected behaviour

using dummy account ,we expect to see auth failed

but we see stucked at start to send TLS

Screenshots

No response

Attempted Fixes

No response

Additional context

No response

Client debug logging

Is your feature request related to a problem? Please describe.

Now that we have net/smtp in our own control, we can implement useful things that weren't possible before (or just very cumbersome). First feature should be debug logging, allowing us see the input and outputs of the SMTP client.

Describe the solution you'd like

The NewClient should get an optional option. If this option is "debug", we want to create a log on the Client and log the different in- and outputs.

Describe alternatives you've considered

No response

Additional context

No response

[Feature Req] Interface that accept To/CC with a single string

Is your feature request related to a problem? Please describe.

Please consider adding/allowing passing a single string to To/CC. Currently there is an extra check preventing it:

failed to set To address: failed to parse mail address "[email protected],[email protected]", mail: expected single address, got ",[email protected]"

When using

if err := m.To("[email protected],[email protected]"); err != nil {

in https://go-mail.dev/getting-started/introduction/#hl-5-13

Reason being:

  • It is perfectly fine to set the "To:" header to something like "Alice <[email protected]>, Bob <[email protected]>, Eve <[email protected]>" in plain email, so such extra checking is too restrictive.
  • I understand that there might be extra checking on the email address format checking, but since the basic text based unix email doesn't check it, such extra checking can be optional, IMHO. After all, if the user made mistakes in their to/cc email address, they will know eventually.
  • And the most important thing is, to add more recipients, I have to change my Go code to do that, whereas the common sense/practice is to change data, not to change code, when covering different cases.

Describe the solution you'd like

Adding/allowing passing a single string to To/CC.

I can work on PR for that.

Describe alternatives you've considered

No response

Additional context

No response

TypeTextPlain lost content

Description

msg = "springboot\n{\n"1.0": "1.0.2.RELEASE"\n}"
receive
lost \n and }

To Reproduce

msg = "springboot\n{\n\"1.0\": \"1.0.2.RELEASE\"\n}"
fmt.Println(msg)
m := mail.NewMsg()
if err := m.From("xx"); err != nil {
	log.Fatalf("failed to set From address: %s", err)
}
if err := m.To("xx"); err != nil {
	log.Fatalf("failed to set To address: %s", err)
}

m.Subject(title)
m.SetBodyString(mail.TypeTextPlain, msg)
c, err := mail.NewClient("xx", mail.WithPort(465), mail.WithSMTPAuth(mail.SMTPAuthPlain),
	mail.WithUsername("xx"), mail.WithPassword("xx"), mail.WithDebugLog(), mail.WithSSLPort(true))
if err != nil {
	log.Fatalf("failed to create mail client: %s", err)
}
if err := c.DialAndSend(m); err != nil {
	log.Fatalf("failed to send mail: %s", err)
}

receive
lost \n and }

Expected behaviour

get
springboot
{
"1.0": "1.0.2.RELEASE"
}

Screenshots

No response

Attempted Fixes

No response

Additional context

No response

How to set Content-ID to attached file?

Is your feature request related to a problem? Please describe.

I need to set Content-ID to use attached image inside the html content. I tried to SetGenHeader on message but it affects all the parts.
Seems to be related with #107

Describe the solution you'd like

Another option WithContentId or WithHeader in EmbedFile or AttachFile

Describe alternatives you've considered

No response

Additional context

No response

Allow sending mails without authentication

Is your feature request related to a problem? Please describe.

There are users who run their smtp servers without authentication (for example in a private network). Currently, this package is unable to connect to such mail servers.

I've tried a few things and got different error messages as a result:

  1. With auth type plain and no username/password:
dial failed: SMTP AUTH failed: 535 Incorrect authentication data
  1. Without the mail.WithSMTPAuth option or uncommenting https://github.com/wneessen/go-mail/blob/main/client.go#L327-L329:
send failed: sending RCPT TO command failed: 550 relay not permitted
  1. With an empty password and username.

None of these three things seem to work.

Related go-vikunja/vikunja#34 (comment)

Describe the solution you'd like

Ideally, there would be an option like mail.WithoutAuthentication which does not even try to authenticate and instead just sends the mail straight away. I thought this would work when removing lines https://github.com/wneessen/go-mail/blob/main/client.go#L327-L329 but it didn't.

Describe alternatives you've considered

No response

Additional context

No response

golangci-lint returns errors

Description

image

Apparently the latest golangci-linter has issues with the error hanling. We need to check this.

To Reproduce

Run golangci-lint

Expected behaviour

No errors

Screenshots

No response

Attempted Fixes

No response

Additional context

No response

Rework TLSPortPolicy()

          This change makes no sense to me? It hard-codes port to 25 for non-TLS connections, it seems to me? There is no way to set `fallbackPort`?

Previous behavior made more sense to me. With TLSOpportunistic, I want to connect to port X, if it supports STARTTLS, use TLS, otherwise, use the same port, but just do not use TLS. Currently, if I use SetTLSPortPolicy(TLSOpportunistic), if the server does not support STARTTLS on port X, it tries to connect to port 25 on that server - which might not even be available (or accessible - firewalls and stuff).

I do not get issue #105 either. Is this about behavior if one does not call WithPort? I think if WithPort is not made, then default should be to connect to port 25 (or 587?) and try STARTTLS on it, if it fails and TLSOpportunistic is set, use no TLS on same port. The behavior to try different ports makes no sense to me. You use one port, but potentially negotiate different configuration of how you use it. And WithPort is then used to configure which port is the one the client uses.

Luckily, WithTLSPolicy still works and if I use WithTLSPolicy it is exactly how I would want. Maybe just remove deprecation on it?

But I do think that connecting to multiple ports is a strange approach. That might be done by some higher-level library to auto-detect configuration of an unknown SMTP server. But not by a low-level library like this one where you generally know the server you are connecting to.

Originally posted by @mitar in #170 (comment)

Refactor variable names for readability

Is your feature request related to a problem? Please describe.

When I started go-mail, I was following the Go best practices document, choosing mostly single-character variable names. Looking at the code base size of go-mail and considering how many people work with the code base and actually contribute to the project now, I think this was the wrong decision, as it makes it hard for contributors to follow the code - especially given that I have not followed the recommendation of using more descriptive variables names for global context variables.

Describe the solution you'd like

A refactor needs to be done, to allow contributors to better understand the code. There is a nice document from Google, describing a code style that should fulfill this purpose: https://google.github.io/styleguide/go/decisions#variable-names

Describe alternatives you've considered

No response

Additional context

No response

Attachment issues

Description

Multiple calls to WriteTo lose files (and embeds).

To Reproduce

Call WriteTo multiple times.

for example, msg.WriteFile and then client.Send(msg)

Expected behaviour

Attachments are written with every call to WriteTo

Screenshots

No response

Attempted Fixes

I haven't attempted any fix yet, but expect that breaking the io.Reader interface to an io.ReadSeeker would be the simplest fix to that issue.

Another way to avoid breaking compatibility would be to call the fileOptions on every call to write, and set the writer in a closure, like this.

		msg.AttachFile(a.Name, func(f *mail.File) {
			buf := bytes.NewBuffer(a.Content)
			f.Writer = func(w io.Writer) (int64, error) {
				return buf.WriteTo(w)
			}
			f.Name = a.Name
			f.Header.Set(string(mail.HeaderContentType), a.ContentType)
		})

If the options are called on WriteTo, subsequent calls to WriteTo get a new writer, or the user can try to seek if that's better.

Additional context

No response

Error from body writer is ignored

Description

If you set a body writer with SetBodyWriter and the writer fails with an error, (*Msg).WriteTo will not return an error itself.

To Reproduce

Run:

package main

import (
	"errors"
	"fmt"
	"io"

	"github.com/wneessen/go-mail"
)

func main() {
	m := mail.NewMsg()

	m.SetBodyWriter(
		mail.TypeTextPlain,
		func(io.Writer) (int64, error) {
			return 0, errors.New("failed!")
		},
	)

	_, err := m.WriteTo(io.Discard)
	fmt.Println(err)
}

Expected behaviour

The above prints out <nil>. I would expect the body writer error to cause WriteTo to return an error, resulting in the program outputting failed! or a wrapped version of it. Right now, there is no indication that an error occurred.

Screenshots

No response

Attempted Fixes

No response

Additional context

No response

Add GetAddrHeader

Is your feature request related to a problem? Please describe.

I am currently porting some code from github.com/go-mail/mail and I noticed that there is no way to access the the individual addrHeader values. The closest option seems to be GetRecipients, but there is no way to distinguish the different recipient types with that. For us, this primarily comes up in test code.

Describe the solution you'd like

I would be fine with something like GetAddrHeader that was similar to GetGenHeader.

Describe alternatives you've considered

  1. GetTo, GetCc, GetBcc, etc. This would be fine for my use case and may more closely follow the current API design.
  2. GetHeader that looked up address headers in addrHeaders and general headers in genHeaders. Right now, it kind of feels like implementation details of the library are leaking out via GetGenHeader. That said, I think if this were to be done, SetHeader should probably be updated to put address headers in addrHeader.

If something like (2) is not done, it might make sense to deprecate SetHeader and add SetGenHeader to make the API more consistent and to make it clear that address headers cannot be set with it. I almost did not notice this when porting to this library.

Additional context

No response

Add eml parser to generate Msg from .eml files

Is your feature request related to a problem? Please describe.

As requested on discord, it would be nice to generate a new Msg from a given .eml file.

Describe the solution you'd like

Ideally the parser would read all headers supported by go-mail and add them to the Msg automatically and then fill the body with the mail contents of the .eml. Parsing attachments could be tricky though.

Describe alternatives you've considered

No response

Additional context

No response

Implement per-part charsets

Is your feature request related to a problem? Please describe.

During the work at #145 I noticed that we always assume the charset for any mime multipart with the same charset as the charset defined for the Msg. While this absolutely makes sense, hypothetically each part can have its own charset.

Describe the solution you'd like

Therefore we should implement a per-part charset option while still relying on the Msg charset being set as default if no different chartset is set the corresponding part.

Describe alternatives you've considered

No response

Additional context

No response

Provide more ways for middleware to interact with mail parts

Is your feature request related to a problem? Please describe.

For the OpenPGP middleware we are currently working on, we need more ways to interact with mail parts. For PGP/Inline for example, we can only support Plain text mails. Therefore we need a way to delete alternative body parts (like text/html).

This ticket does not yet provide what is all needed, so it will be used as collective issue for all enhancements that are going to be developed.

Describe the solution you'd like

We need ways to interact with/modify body parts.

Describe alternatives you've considered

No response

Additional context

No response

client.Send() returns is one of the given messages fails

Description

We have the possibility to send multiple mails using Client.Send(). Unfortunately, when processing the messages, once a processing failed Send will return with an error and not process any further. This can result into messages not being processed.

To Reproduce

This test can simulate the issue.

// TestClient_Send_withBrokenRecipient tests the Send() method of Client with a broken and a working recipient
func TestClient_Send_withBrokenRecipient(t *testing.T) {
	if os.Getenv("TEST_ALLOW_SEND") == "" {
		t.Skipf("TEST_ALLOW_SEND is not set. Skipping mail sending test")
	}
	var msgs []*Msg
	rcpts := []string{"[email protected]", TestRcpt, "[email protected]"}
	for _, rcpt := range rcpts {
		m := NewMsg()
		_ = m.FromFormat("go-mail Test Mailer", os.Getenv("TEST_FROM"))
		_ = m.To(rcpt)
		m.Subject(fmt.Sprintf("This is a test mail from go-mail/v%s", VERSION))
		m.SetBulk()
		m.SetDate()
		m.SetMessageID()
		m.SetBodyString(TypeTextPlain, "This is a test mail from the go-mail library")
		msgs = append(msgs, m)
	}

	c, err := getTestConnection(true)
	if err != nil {
		t.Skipf("failed to create test client: %s. Skipping tests", err)
	}

	ctx, cfn := context.WithTimeout(context.Background(), DefaultTimeout)
	defer cfn()
	if err := c.DialWithContext(ctx); err != nil {
		t.Errorf("failed to dial to sending server: %s", err)
	}
	if err := c.Send(msgs...); err != nil {
		t.Errorf("failed to send out messages: %s", err)
	}
	if err := c.Close(); err != nil {
		t.Errorf("failed to close client connection: %s", err)
	}
}

Expected behaviour

Errors should be collected and returned as once after all messages have been proceessed.

Screenshots

No response

Attempted Fixes

No response

Additional context

No response

Store generated mail as file

Is your feature request related to a problem? Please describe.

No response

Describe the solution you'd like

I found this issue go-gomail/gomail#171 and thought it might be nice addition to the library being able to store generated mails as files. Since we already have Msg.WriteTo it should be fairly simple to realize.

I'll have to check the EML specifications if there is any file sepecific formating needed, though.

Describe alternatives you've considered

No response

Additional context

No response

Ability for preformated headers

Is your feature request related to a problem? Please describe.

For the DKIM implementation I am working on, I need a way to set mail headers in a preformated way. Since DKIM calculates a checksum and a signature on the given mail headers and then updates a header, the formating of the header might change due to the fact that there are now more characters involved causing the automatic line breaking to chime in.

As a result the checksum and/signature might not be matching anymore.

Describe the solution you'd like

We need some way to add preformated headers to the mail message, that get not affected by the automatic line breaks.

Describe alternatives you've considered

No response

Additional context

No response

Provide a way of knowing which emails failed/succeeded in sending

Is your feature request related to a problem? Please describe.

When sending multiple emails, an error is returned when any one of them fails. How do I know which one failed? I have, let's say, 100 emails to send. I get an error back ... now what? Which one(s) do I try resending? Which ones do I mark internally as having been sent / failed to send?

Describe the solution you'd like

Perhaps it makes sense to return an array of errors, 1:1 with the messages, guaranteed to be the same length as the messages? That makes it slightly annoying for the checkConn-type global failure. OTOH, this not exactly the common case, so OK to just make N copies of that error in an array? Not exactly idiomatic Go, but I don't know what is in this case.

Describe alternatives you've considered

Perhaps something can be added to the Msg itself indicating send status? Seems dodgy to mutate inputs though, and what happens if the object is reused somehow (e.g. resent), could make it unclear whether the status is "correct" or not.

Additional context

Note that I'm not using this library yet. Just shopping around for a lib that can send multiple emails in a single connection.

Add method to remove any attachment/embed

Is your feature request related to a problem? Please describe.

Based on this issue it would be nice to have a method that removes all attachments/embeds that are currently attached to a Msg. This should be a simple change.

Describe the solution you'd like

I am thinking about three seperate methods:

  • Msg.UnsetAllAttachments(): That removes all currently assigned attachments
  • Msg.UnsetAllEmbeds(): That removes all currently assigned embeds
  • Msg.UnsetAllParts(): That removes all currently assigned attachments and embeds

Describe alternatives you've considered

No response

Additional context

No response

OAuth2 support

Is your feature request related to a problem? Please describe.

First of all thank you for this amazing library. Finally, a well-maintained library for sending emails with Go.
I'm using this library in SFTPGo since version 2.5.0 and it works fine.
I was wondering if there is a plan to add OAuth2 support:

https://developers.google.com/gmail/imap/xoauth2-protocol
https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth

Describe the solution you'd like

The Golang oauth2 library could be used to implement the token exchange. Once we have the token the library should be able to implement and use the log in with the XOAUTH2 mechanism.

I'm not sure if this feature should be added here, in a plugin or is outside the scope of this project

Describe alternatives you've considered

No response

Additional context

No response

Expose Msg fields

Is your feature request related to a problem? Please describe.

In order to use middlewares we need to have access to the Msg fields. For example, in my use case I want to add OpenPGP support so I need to manipulate the parts of Msg.

Describe the solution you'd like

I propose either to make Msg field public. Or add theirs Get and Set methods.
WDYT?

Describe alternatives you've considered

No response

Additional context

No response

Use appropriate standard port when setting SSL/TLS

Is your feature request related to a problem? Please describe.

When using SSL or STARTTLS, the default port 25 is always used, which is against best practices as described in RFC8314, section 3.3.

I'm willing to implement and send a PR if this enhancement is accepted.

Describe the solution you'd like

  1. WithSSL() to use the port default of 465, unless WithPort() is called;
  2. WithTLSPolicy() with TLSMandatory or TLSOpportunistic to use port 587, and in case of the latter use fallback to port 25;

Describe alternatives you've considered

Explicitly setting the port number with WithPort(587) works in my case. My mail server does not allow fallback to plain text SMTP on port 25. But if it where the case, I expect that TLSOpportunistic would not work as intended, as it will always try to use port 587, instead of falling back to 25.

Additional context

To reproduce:

client, err := mail.NewClient(host,
	mail.WithSSL(),
	mail.WithSMTPAuth(mail.SMTPAuthPlain),
	mail.WithUsername(user),
	mail.WithPassword(password),
)

Or

client, err := mail.NewClient(host,
	mail.WithTLSPolicy(mail.TLSMandatory),
	mail.WithSMTPAuth(mail.SMTPAuthPlain),
	mail.WithUsername(user),
	mail.WithPassword(password),
)

Will result in connection failures to compliant hosts.

The above was inspired by the bulk mailer example, which doesn't seem to set an alternative port either.

DialAndSend with custom context

Is your feature request related to a problem? Please describe.

Currently DialAndSend does not offer the ability to provide the user's own context. The user is encouraged to use DialWithContext and the Send. DialAndSend in fact currently uses context.Background for its context. On Discord it was suggested to have a DialAndSend with custom context.

Describe the solution you'd like

A DialAndSendWithContext should be introduced. DialAndSend could make use of this to avoid duplicate code and the chance of breaking the current API

Describe alternatives you've considered

No response

Additional context

No response

Implement io.Reader interface on msg

I thought about using io.Copy for convenience reasons, such as dumping a message to log writer, but tbh this is not something I really need. I'm perfectly fine with current API =) However, as Msg now satisfies io.WriterTo it seems strange that you can't use it as an argument for widely used io.Copy. I think adding this makes sense.

Originally posted by @inliquid in #3 (reply in thread)

Add possibility to attach/embed templates

I occasionally check the go-mail/mail issue tracker if people run into problems, that we might want to address in wneessen/go-mail. I stumbled upon go-mail/mail#74 and thought it would be a good idea to implement a simple way to attach/embed template.Template to Msg objects.

LOGIN AUTH next handler should unconditionally succeed if more is false

Description

Looking at the following code portions:

It appears to me that when the more is false, then the authentication should be considered successful. Let me give my reasoning step by step:

1, Next is called here if err is nil.
2. The err var is only potentially set here as non-nil as it was prechecked here
3. Here are the possible codes (334 and 235) which are NOT an error and this seems to align with the spec
4. Here we see that more is set true if code is 334, so we can assume a 235 if it's not set and the Next implementation was called.

To Reproduce

Use a SMTP Server which doesn't respond with the suffix Authentication successful and instead uses something like Authentication succeeded. Example ProtonMail Bridge.

Expected behaviour

If a server responds with an irregular response as long as the response follows the spec that it's dealt with accordingly.

I'm happy to fix this, but wanted to see what you thought first and maybe there's a reason this has been done that I'm no aware of, the SMTP landscape is a bit wild west.

Screenshots

No response

Attempted Fixes

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case ServerRespUsername:
			return []byte(a.username), nil
		case ServerRespPassword:
			return []byte(a.password), nil
		default:
			return nil, fmt.Errorf("unexpected server response: %s", string(fromServer))
		}
	}

	return nil, nil
}

Additional context

Introduced In:

I wish Next included the SMTP code for starters, and that SMTP servers used the examples in the spec.. but officially a 235 indicates a successful response. See RFC2554 Section 4

Validate that go-mail is not vulnerable to email content tampering

Is your feature request related to a problem? Please describe.

In the legacy gomail project someone reported a vulnerabilty that allows email content tampering via specially crafted filenames: go-gomail/gomail#190

While I am pretty sure that we should not be vulnerable, since our attachment writer is completely different to gomail's, we need to double check this and, in case of a confirmed vulnerability, fix it accordingly.

Describe the solution you'd like

Properly escaped/encoded filenames in the Content-Disposition and Content-Type headers

Describe alternatives you've considered

No response

Additional context

No response

Improve test coverage

Is your feature request related to a problem? Please describe.

We currently have about 82% test coverage which covers most of the possible cases. We might run into some edge cases thought, which might more precise coverage. Let's try to get this number up where possible.

Describe the solution you'd like

Better test coverage

Describe alternatives you've considered

No response

Additional context

No response

Add possibility to set dedicated envelope from and mail body from address

Is your feature request related to a problem? Please describe.

In some scenarios we want to use a different envelope from address than the mail body from address that is presented in the MUA (i. e. marketing campaigns may want to use a different envelope from address to record bounces)

Describe the solution you'd like

We should have a way to set a dedicated envelope from address. If not set, the mail body from address will be used instead. If only envelope from is set, it will be used in both envelope and mail body.

Describe alternatives you've considered

No response

Additional context

No response

Using defer inside for loop could lead to leaks

Description

As mentioned in #116

you create resources inside for loop and defer inside for loop, the defer function will execute after function returns, so you won't be able to clean resources inside for loop, it all will wait until function exists. I put close after for loop ended. so it will release the resources after for loop ends.

The initial author doesn't found the time to fix this completely, so this issue is meant to address the issue.

To Reproduce

N/A

Expected behaviour

N/A

Screenshots

No response

Attempted Fixes

No response

Additional context

No response

Evaluate using a logger interface instead of golang stdlib log package

Is your feature request related to a problem? Please describe.

As stated in #102 (comment), we should rather use a logger intreface, so the user can decide which kind of logger they want to use.

Describe the solution you'd like

We provide a simple Logger interface that can be implemented easily:

type Logger interface {
	Errorf(format string, v ...interface{})
	Warnf(format string, v ...interface{})
	Infof(format string, v ...interface{})
	Debugf(format string, v ...interface{})
}

Describe alternatives you've considered

No response

Additional context

No response

Method SetBodyHTML, without Template

Is your feature request related to a problem? Please describe.

The m.SetBodyHTMLTemplate would make it very difficult to programmatically switching between sending plain text or html email, while having a m.SetBodyHTML would make the task trivial.

Describe the solution you'd like

Can we have m.SetBodyHTML that accept a html body string, instead of a template and a d interface{} please?

Please consider. thx.
I can work on the PR.

Describe alternatives you've considered

No response

Additional context

No response

Set up FreeBSD tests via Cirrus-CI

Is your feature request related to a problem? Please describe.

We are currently not testing on FreeBSD since Github Actions don't support it. We can use Cirrus-CI for it though.

Describe the solution you'd like

Install Cirrus-CI config and run FreeBSD tests

Describe alternatives you've considered

No response

Additional context

No response

Customize smtp traffic log format

Is your feature request related to a problem? Please describe.

No response

Describe the solution you'd like

pass a format string and logging with the format.

Describe alternatives you've considered

fork and chang the log message in forked repo

Additional context

No response

Body of message is not written in second call to `Write`

Just found an interesting issue. In my scenario I first call Msg.Write to log message contents to debug logger and after that Msg.WriteToSendmailWithContext does not populate body of the message. Removing first call to Msg.Write or making two sequential calls to Msg.Write proves that. It seems that underlying writer shifts its current position to the end of buffer preventing body from being read second time. In original gomail this works normally. Didn't have much time to look into it.

Fork the net/smtp package from stdlib into go-mail

Is your feature request related to a problem? Please describe.

go-mail relies on the net/smtp package of the Go stdlib. Unfortunately this lib is in feature freeze mode, so that enhancing/extending the package is not possible. This restricts us - at least to a certain level - as we can't add features that would be useful for go-mail (thinking about giving back SMTP status codes or perform debug-logging on the client level).

For that reason it might be a good idea for go-mail to fork the net/smtp package into our own code base. This would allow us to start with a solid code base that we already rely on but it would also allow us to add features and extend the code.

License-wise the BSD-3 license of the Go project would allow this and since the Go stdlib is in code freeze, we don't need to worry about keeping up with the original code - monitoring for possible security fixes should be put into place maybe.

Describe the solution you'd like

Get net/smtp forked into go-mail and adjust any code that currently use net/smtp to use our internal fork instead.

Describe alternatives you've considered

N/A

Additional context

N/A

Implement structured JSON logger

Is your feature request related to a problem? Please describe.

No

Describe the solution you'd like

With the addition of log/slog to the std lib, we can add an additional standard logger to the go-mail log package, that allows structured logging in JSON formatting.

Describe alternatives you've considered

No response

Additional context

No response

Allow direct embedding of embed.FS files

Is your feature request related to a problem? Please describe.

In this discussion: #27 (reply in thread) I was made aware, that users might want to embed/attach files via embed.FS - meaning files that are directly shipped in the go binary. This is currently possible via opening the embed.FS file and then using EmbedReader. We might consider adding direct embed.FS support for ease of use.

Describe the solution you'd like

I see two possible solutions:

  • Add two new EmbedEmbedFSFile() and AttachEmbedFSFile() methods
  • Add a new FileOption that hands over the embed.FS to either EmbedFile() and AttachFile() methods that then decide from where to fetch the file.

Describe alternatives you've considered

No response

Additional context

No response

Reuse compliance

Is your feature request related to a problem? Please describe.

TIL learned about Reuse: https://reuse.software/dev/

Let's make go-mail Reuse compliant, so other projects can implement it without running into licensing issue. Even though the library does not include any 3rd parties. We can make it easy for other projects.

Describe the solution you'd like

Implement reuse compliance.

Describe alternatives you've considered

No response

Additional context

No response

allow creating html mail with plain text part

Is your feature request related to a problem? Please describe.

Currently you can create a html mail or a plain text mail.
Without fallback solution.

Describe the solution you'd like

It would be nice to have an option to create a html body with a fallback plain text (for email programs that are not supporting html)

https://gist.github.com/tylermakin/d820f65eb3c9dd98d58721c7fb1939a8

Describe alternatives you've considered

No response

Additional context

No response

segfault in writer because CreatePart error is ignored

Description

https://github.com/wneessen/go-mail/blob/main/msgwriter.go#L240

this error is ignored, leading to a crash here

https://github.com/wneessen/go-mail/blob/main/msgwriter.go#L234C1-L235C1

goroutine 11 [running]:
bytes.(*Buffer).WriteTo(0xc00012dd10, {0x0?, 0x0?})
    /usr/local/go/src/bytes/buffer.go:252 +0x5c
io.copyBuffer({0x0, 0x0}, {0xac7900, 0xc00012dd10}, {0x0, 0x0, 0x0})
    /usr/local/go/src/io/io.go:409 +0x16e
io.Copy(...)
    /usr/local/go/src/io/io.go:386
github.com/wneessen/go-mail.(*msgWriter).writeBody(0xc0007ec770, 0xc00006b2c0, {0x9ffa26, 0x6})
    /go/pkg/mod/github.com/wneessen/[email protected]/msgwriter.go:330 +0x597
github.com/wneessen/go-mail.(*msgWriter).addFiles(0xc0007ec770, {0xc00012cea0, 0x6, 0x5?}, 0x1)
    /go/pkg/mod/github.com/wneessen/[email protected]/msgwriter.go:209 +0x6a
github.com/wneessen/go-mail.(*msgWriter).writeMsg(0xc0007ec770, 0xc00017c000)
    /go/pkg/mod/github.com/wneessen/[email protected]/msgwriter.go:118 +0x6bc
github.com/wneessen/go-mail.(*Msg).WriteTo(0xc00017c000, {0x7fdc9107e890?, 0xc0007ee630})
    /go/pkg/mod/github.com/wneessen/[email protected]/msg.go:814 +0xbc
github.com/wneessen/go-mail.(*Client).Send(0xc0001fe000, {0xc0001592b8, 0x1, 0x37?})
    /go/pkg/mod/github.com/wneessen/[email protected]/client_119.go:78 +0x124b
github.com/wneessen/go-mail.(*Client).DialAndSendWithContext(0xc000032052?, {0xacb778?, 0xc000068000?}, {0xc0001592b8, 0x1, 0x1})

To Reproduce

hard to do. this happens with some broken mail server that just times out.

Expected behaviour

error shouldnt be ignored

Screenshots

No response

Attempted Fixes

No response

Additional context

No response

Improve error handling

          > Since errors are appendable

BTW, the way how code currently does "appending" is really ... bad. I think. As an author of one of (I believe) good errors packages for Go the issue with current approach is that you create a tree of joined errors. Every time you call errors.Join it creates a new error with two child errors.

A better way I think would be to have a slice of errors you append to and join just once at the end (you could even join in defer and set a named return value. errors.Join also discards all nil errors, so you could just be collecting all errors without checking (unless it influences the code flow). I did something similar here. Maybe the best way would be to extract inner loop code to sendOne internal function which returns error as usual. And then you collect errors in the loop and call final errors.Join.

Originally posted by @mitar in #166 (comment)

MessageID generation not random enough

Description

When in a bulk mailing, a list of *mail.Msg is prepared with a message ID in a loop, the processing might be so fast, that the message ID for the message IDs is not random. This might cause issues if mails are delivered to the same person on the same server with one address being forward to the 2nd mail address. Most mail server would "combine" this into one mail. While this might be the wanted behaviour from a mail receiving perspective, from a sending perspectice this is not right. The message ID needs to be unique.

This needs some further investigation.

To Reproduce

This came up in the mail example for discussion: #73

Expected behaviour

A unique and random message id is to be generated for every message.

Screenshots

No response

Attempted Fixes

No response

Additional context

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.