Hi there, I am Winni 👋
I work in InfoSec and do open source development in my spare time. Music-lover, Record-collector, Go-Enthusiast, Perl-veteran. Vir potens spiritus.
📧 Easy to use, yet comprehensive library for sending mails with Go
Home Page: https://go-mail.dev
License: MIT License
I work in InfoSec and do open source development in my spare time. Music-lover, Record-collector, Go-Enthusiast, Perl-veteran. Vir potens spiritus.
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?
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.
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.
Note that I'm not using this library yet. Just shopping around for a lib that can send multiple emails in a single connection.
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.
If you set a body writer with SetBodyWriter
and the writer fails with an error, (*Msg).WriteTo
will not return an error itself.
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)
}
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.
No response
No response
No response
> 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)
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.
Get net/smtp
forked into go-mail and adjust any code that currently use net/smtp
to use our internal fork instead.
N/A
N/A
Currently you can create a html mail or a plain text mail.
Without fallback solution.
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
No response
No response
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.
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.
This came up in the mail example for discussion: #73
A unique and random message id is to be generated for every message.
No response
No response
No response
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.
I am thinking about three seperate methods:
Msg.UnsetAllAttachments()
: That removes all currently assigned attachmentsMsg.UnsetAllEmbeds()
: That removes all currently assigned embedsMsg.UnsetAllParts()
: That removes all currently assigned attachments and embedsNo response
No response
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.
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.
I could be calling Client.Send
on each message individually myself. But that then calls checkConn
again and again.
No response
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})
hard to do. this happens with some broken mail server that just times out.
error shouldnt be ignored
No response
No response
No response
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:
dial failed: SMTP AUTH failed: 535 Incorrect authentication data
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
None of these three things seem to work.
Related go-vikunja/vikunja#34 (comment)
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.
No response
No response
As requested on discord, it would be nice to generate a new Msg
from a given .eml
file.
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.
No response
No response
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.
Use a SMTP Server which doesn't respond with the suffix Authentication successful
and instead uses something like Authentication succeeded
. Example ProtonMail Bridge
.
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.
No response
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
}
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
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.
Better test coverage
No response
No response
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.
Properly escaped/encoded filenames in the Content-Disposition
and Content-Type
headers
No response
No response
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)
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.
N/A
N/A
No response
No response
No response
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)
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.
No response
No response
No response
pass a format string and logging with the format.
fork and chang the log message in forked repo
No response
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
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
No response
No response
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
use dummy account and password to connect to smtp.exmail.qq.com:587 and send email
using dummy account ,we expect to see auth failed
but we see stucked at start to send TLS
No response
No response
No response
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.
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.
No response
No response
Upstream net/smtp
has received a very minior change:
golang/go@eb2bc91#diff-2306577ab99a109f7df7c52a6ba6d4c22bfe20168bb22692920fc95c77bb4aed
This should be synced to our port as well.
N/A
No response
No response
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:
To:
" header to something like "Alice <[email protected]>, Bob <[email protected]>, Eve <[email protected]>"
in plain email, so such extra checking is too restrictive.Adding/allowing passing a single string to To/CC.
I can work on PR for that.
No response
No response
msg = "springboot\n{\n"1.0": "1.0.2.RELEASE"\n}"
receive
lost \n and }
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 }
get
springboot
{
"1.0": "1.0.2.RELEASE"
}
No response
No response
No response
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.
A DialAndSendWithContext
should be introduced. DialAndSend
could make use of this to avoid duplicate code and the chance of breaking the current API
No response
No response
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.
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.
No response
No response
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.
WithSSL()
to use the port default of 465, unless WithPort()
is called;WithTLSPolicy()
with TLSMandatory
or TLSOpportunistic
to use port 587, and in case of the latter use fallback to port 25;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.
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.
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
Another option WithContentId or WithHeader in EmbedFile or AttachFile
No response
No response
Maybe easier is to define `SMTPAuthType` `None` which equals to an empty string. This is what I did in my own codebase now.
Originally posted by @mitar in #36 (comment)
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.
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.
No response
No response
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.
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{})
}
No response
No response
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.
Solution should allow signing mails with domain key headers.
No response
No response
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.
I see two possible solutions:
EmbedEmbedFSFile()
and AttachEmbedFSFile()
methodsFileOption
that hands over the embed.FS to either EmbedFile()
and AttachFile()
methods that then decide from where to fetch the file.No response
No response
No
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.
No response
No response
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.
I propose either to make Msg
field public. Or add theirs Get and Set methods.
WDYT?
No response
No response
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.
We need some way to add preformated headers to the mail message, that get not affected by the automatic line breaks.
No response
No response
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--
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
No bounces due to syntax error in body.
No response
No response
No response
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.
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
No response
No response
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.
No response
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.
No response
No response
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)
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.
Implement reuse compliance.
No response
No response
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.
We need ways to interact with/modify body parts.
No response
No response
Multiple calls to WriteTo
lose files (and embeds).
Call WriteTo
multiple times.
for example, msg.WriteFile
and then client.Send(msg)
Attachments are written with every call to WriteTo
No response
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.
No response
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.
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)
}
}
Errors should be collected and returned as once after all messages have been proceessed.
No response
No response
No response
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.
I would be fine with something like GetAddrHeader
that was similar to GetGenHeader
.
GetTo
, GetCc
, GetBcc
, etc. This would be fine for my use case and may more closely follow the current API design.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.
No response
We are currently not testing on FreeBSD since Github Actions don't support it. We can use Cirrus-CI for it though.
Install Cirrus-CI config and run FreeBSD tests
No response
No response
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.