From a2a4cfd168b37b591aa7a266a7f70f1d6952fb01 Mon Sep 17 00:00:00 2001
From: Vilen Topchii <32271530+vtopc@users.noreply.github.com>
Date: Sun, 15 Dec 2024 20:18:29 +0200
Subject: [PATCH] DE-1388 (mg *MailgunImpl) Send should accept interface, not
struct (#368)
https://bryanftan.medium.com/accept-interfaces-return-structs-in-go-d4cab29a301b
---
README.md | 6 +-
attachments_test.go | 2 +-
events_test.go | 2 +-
examples/examples.go | 6 +-
examples_test.go | 2 +-
mailgun.go | 5 +-
messages.go | 294 +++++++++++++++++--------------------------
messages_test.go | 40 +++---
messages_types_v5.go | 3 +-
messages_v5.go | 5 +-
storage_test.go | 2 +-
tags_test.go | 2 +-
12 files changed, 156 insertions(+), 213 deletions(-)
diff --git a/README.md b/README.md
index f78c9fe3..f5407204 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ func main() {
recipient := "recipient@example.com"
// The message object allows you to add attachments and Bcc recipients
- message := mailgun.NewMessage(sender, subject, body, recipient)
+ message := mailgun.NewMessage(yourDomain, sender, subject, body, recipient)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
@@ -290,7 +290,7 @@ func main() {
subject := "HTML email!"
recipient := "recipient@example.com"
- message := mailgun.NewMessage(sender, subject, "", recipient)
+ message := mailgun.NewMessage(yourDomain, sender, subject, "", recipient)
body := `
@@ -351,7 +351,7 @@ func main() {
recipient := "recipient@example.com"
// The message object allows you to add attachments and Bcc recipients
- message := mailgun.NewMessage(sender, subject, body, recipient)
+ message := mailgun.NewMessage(yourDomain, sender, subject, body, recipient)
message.SetTemplate("passwordReset")
err := message.AddTemplateVariable("passwordResetLink", "some link to your site unique to your user")
if err != nil {
diff --git a/attachments_test.go b/attachments_test.go
index 9812eb85..e307b088 100644
--- a/attachments_test.go
+++ b/attachments_test.go
@@ -31,7 +31,7 @@ func TestMultipleAttachments(t *testing.T) {
var ctx = context.Background()
- m := mailgun.NewMessage("root@"+testDomain, "Subject", "Text Body", "attachment@"+testDomain)
+ m := mailgun.NewMessage(testDomain, "root@"+testDomain, "Subject", "Text Body", "attachment@"+testDomain)
// Add 2 attachments
m.AddAttachment(createAttachment(t))
diff --git a/events_test.go b/events_test.go
index 2621ff67..a8eeb3e4 100644
--- a/events_test.go
+++ b/events_test.go
@@ -87,7 +87,7 @@ func TestEventPoller(t *testing.T) {
}()
// Send an email
- m := mailgun.NewMessage("root@"+testDomain, "Subject", "Text Body", "user@"+testDomain)
+ m := mailgun.NewMessage(testDomain, "root@"+testDomain, "Subject", "Text Body", "user@"+testDomain)
msg, id, err := mg.Send(ctx, m)
require.NoError(t, err)
diff --git a/examples/examples.go b/examples/examples.go
index 8c43abef..da15c7e6 100644
--- a/examples/examples.go
+++ b/examples/examples.go
@@ -779,7 +779,7 @@ func SendMimeMessage(domain, apiKey string) (string, error) {
return "", err
}
- m := mailgun.NewMIMEMessage(mimeMsgReader, "bar@example.com")
+ m := mailgun.NewMIMEMessage(domain, mimeMsgReader, "bar@example.com")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
@@ -824,6 +824,7 @@ func SendSimpleMessage(domain, apiKey string) (string, error) {
func SendTaggedMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mailgun.NewMessage(
+ domain,
"Excited User ",
"Hello",
"Testing some Mailgun awesomeness!",
@@ -845,6 +846,7 @@ func SendTaggedMessage(domain, apiKey string) (string, error) {
func SendTemplateMessage(domain, apiKey string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mailgun.NewMessage(
+ domain,
"Excited User ",
"Hey %recipient.first%",
"If you wish to unsubscribe, click http://mailgun/unsubscribe/%recipient.id%",
@@ -939,7 +941,7 @@ func SendMessageWithTemplate(domain, apiKey string) error {
time.Sleep(time.Second * 1)
// Create a new message with template
- m := mailgun.NewMessage("Excited User ", "Template example", "")
+ m := mailgun.NewMessage(domain, "Excited User ", "Template example", "")
m.SetTemplate("my-template")
// Add recipients
diff --git a/examples_test.go b/examples_test.go
index 216b8c09..7018765c 100644
--- a/examples_test.go
+++ b/examples_test.go
@@ -112,7 +112,7 @@ Testing some Mailgun MIME awesomeness!
defer cancel()
mg := mailgun.NewMailgun("example.com", "my_api_key")
- m := mailgun.NewMIMEMessage(io.NopCloser(strings.NewReader(exampleMime)), "bargle.garf@example.com")
+ m := mailgun.NewMIMEMessage("example.com", io.NopCloser(strings.NewReader(exampleMime)), "bargle.garf@example.com")
_, id, err := mg.Send(ctx, m)
if err != nil {
log.Fatal(err)
diff --git a/mailgun.go b/mailgun.go
index a11927ce..2b8bf7ff 100644
--- a/mailgun.go
+++ b/mailgun.go
@@ -139,9 +139,8 @@ type Mailgun interface {
AddOverrideHeader(k string, v string)
GetCurlOutput() string
- // Send attempts to queue a message (see Message, NewMessage, and its methods) for delivery.
- // TODO(v5): switch m to SendableMessage interface
- Send(ctx context.Context, m *Message) (mes string, id string, err error)
+ // Send attempts to queue a message (see CommonMessage, NewMessage, and its methods) for delivery.
+ Send(ctx context.Context, m SendableMessage) (mes string, id string, err error)
ReSend(ctx context.Context, id string, recipients ...string) (string, string, error)
ListBounces(opts *ListOptions) *BouncesIterator
diff --git a/messages.go b/messages.go
index c68bcf1a..918e31bd 100644
--- a/messages.go
+++ b/messages.go
@@ -19,21 +19,19 @@ const MaxNumberOfRecipients = 1000
// MaxNumberOfTags represents the maximum number of tags that can be added for a message
const MaxNumberOfTags = 3
-// Message structures contain both the message text and the envelope for an e-mail message.
-// TODO(v5): rename to CommonMessage and remove Specific
-type Message struct {
- to []string
- tags []string
- campaigns []string
- dkim *bool
- deliveryTime time.Time
- stoPeriod string
- attachments []string
- readerAttachments []ReaderAttachment
- inlines []string
- readerInlines []ReaderAttachment
- bufferAttachments []BufferAttachment
-
+// CommonMessage structures contain both the message text and the envelope for an e-mail message.
+type CommonMessage struct {
+ domain string
+ to []string
+ tags []string
+ dkim *bool
+ deliveryTime time.Time
+ stoPeriod string
+ attachments []string
+ readerAttachments []ReaderAttachment
+ inlines []string
+ readerInlines []ReaderAttachment
+ bufferAttachments []BufferAttachment
nativeSend bool
testMode bool
tracking *bool
@@ -43,14 +41,10 @@ type Message struct {
variables map[string]string
templateVariables map[string]any
recipientVariables map[string]map[string]any
- domain string
templateVersionTag string
templateRenderText bool
-
- requireTLS bool
- skipVerification bool
-
- Specific
+ requireTLS bool
+ skipVerification bool
}
type ReaderAttachment struct {
@@ -67,8 +61,9 @@ type BufferAttachment struct {
// You're expected to use various setters to set most of these attributes,
// although from, subject, and text are set when the message is created with
// NewMessage.
-// TODO(v5): embed CommonMessage
type PlainMessage struct {
+ CommonMessage
+
from string
cc []string
bcc []string
@@ -112,8 +107,9 @@ func (m *PlainMessage) Template() string {
}
// MimeMessage contains fields relevant to pre-packaged MIME messages.
-// TODO(v5): embed CommonMessage
type MimeMessage struct {
+ CommonMessage
+
body io.ReadCloser
}
@@ -122,14 +118,14 @@ type sendMessageResponse struct {
Id string `json:"id"`
}
-// TrackingOptions contains fields relevant to trackings.
+// TrackingOptions contains fields relevant to tracking.
type TrackingOptions struct {
Tracking bool
TrackingClicks string
TrackingOpens bool
}
-// Specific abstracts the common characteristics between regular and MIME messages.
+// Specific abstracts the common characteristics between plain text and MIME messages.
type Specific interface {
// AddCC appends a receiver to the carbon-copy header of a message.
AddCC(string)
@@ -142,7 +138,7 @@ type Specific interface {
SetHTML(string)
// SetAmpHTML If you're sending a message that isn't already MIME encoded, it will arrange to bundle
- // an AMP-For-Email representation of your message in addition to your html & plain-text content.
+ // an AMP-For-Email representation of your message in addition to your HTML & plain-text content.
SetAmpHTML(string)
// AddValues invoked by Send() to add message-type-specific MIME headers for the API call
@@ -172,11 +168,6 @@ type Specific interface {
// SetTemplate sets the name of a template stored via the template API.
// See https://documentation.mailgun.com/docs/mailgun/user-manual/sending-messages/#templates
SetTemplate(string)
-
- // // TODO(v5):
- // // AddRecipient appends a receiver to the To: header of a message.
- // // It will return an error if the limit of recipients have been exceeded for this message
- // AddRecipient(recipient string) error
}
// NewMessage returns a new e-mail message with the simplest envelop needed to send.
@@ -188,19 +179,20 @@ type Specific interface {
// You can do this explicitly, or implicitly, as follows:
//
// // Note absence of `to` parameter(s)!
-// m := NewMessage("me@example.com", "Help save our planet", "Hello world!")
+// m := NewMessage("example.com", "me@example.com", "Help save our planet", "Hello world!")
//
// Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method
// before sending, though.
-// TODO(v5): should return PlainMessage
-func NewMessage(from, subject, text string, to ...string) *Message {
- return &Message{
- Specific: &PlainMessage{
- from: from,
- subject: subject,
- text: text,
+func NewMessage(domain, from, subject, text string, to ...string) *PlainMessage {
+ return &PlainMessage{
+ CommonMessage: CommonMessage{
+ domain: domain,
+ to: to,
},
- to: to,
+
+ from: from,
+ subject: subject,
+ text: text,
}
}
@@ -215,122 +207,118 @@ func NewMessage(from, subject, text string, to ...string) *Message {
// You can do this explicitly, or implicitly, as follows:
//
// // Note absence of `to` parameter(s)!
-// m := NewMIMEMessage(body)
+// m := NewMIMEMessage(domain, body)
//
// Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method
// before sending, though.
-// TODO(v5): should return MimeMessage
-func NewMIMEMessage(body io.ReadCloser, to ...string) *Message {
- return &Message{
- Specific: &MimeMessage{
- body: body,
+func NewMIMEMessage(domain string, body io.ReadCloser, to ...string) *MimeMessage {
+ return &MimeMessage{
+ CommonMessage: CommonMessage{
+ domain: domain,
+ to: to,
},
- to: to,
+ body: body,
}
}
-func (m *Message) Domain() string {
+func (m *CommonMessage) Domain() string {
return m.domain
}
-func (m *Message) To() []string {
+func (m *CommonMessage) To() []string {
return m.to
}
-func (m *Message) Tags() []string {
+func (m *CommonMessage) Tags() []string {
return m.tags
}
-func (m *Message) Campaigns() []string {
- return m.campaigns
-}
-
-func (m *Message) DKIM() *bool {
+func (m *CommonMessage) DKIM() *bool {
return m.dkim
}
-func (m *Message) DeliveryTime() time.Time {
+func (m *CommonMessage) DeliveryTime() time.Time {
return m.deliveryTime
}
-func (m *Message) STOPeriod() string {
+func (m *CommonMessage) STOPeriod() string {
return m.stoPeriod
}
-func (m *Message) Attachments() []string {
+func (m *CommonMessage) Attachments() []string {
return m.attachments
}
-func (m *Message) ReaderAttachments() []ReaderAttachment {
+func (m *CommonMessage) ReaderAttachments() []ReaderAttachment {
return m.readerAttachments
}
-func (m *Message) Inlines() []string {
+func (m *CommonMessage) Inlines() []string {
return m.inlines
}
-func (m *Message) ReaderInlines() []ReaderAttachment {
+func (m *CommonMessage) ReaderInlines() []ReaderAttachment {
return m.readerInlines
}
-func (m *Message) BufferAttachments() []BufferAttachment {
+func (m *CommonMessage) BufferAttachments() []BufferAttachment {
return m.bufferAttachments
}
-func (m *Message) NativeSend() bool {
+func (m *CommonMessage) NativeSend() bool {
return m.nativeSend
}
-func (m *Message) TestMode() bool {
+func (m *CommonMessage) TestMode() bool {
return m.testMode
}
-func (m *Message) Tracking() *bool {
+func (m *CommonMessage) Tracking() *bool {
return m.tracking
}
-func (m *Message) TrackingClicks() *string {
+func (m *CommonMessage) TrackingClicks() *string {
return m.trackingClicks
}
-func (m *Message) TrackingOpens() *bool {
+func (m *CommonMessage) TrackingOpens() *bool {
return m.trackingOpens
}
-func (m *Message) Variables() map[string]string {
+func (m *CommonMessage) Variables() map[string]string {
return m.variables
}
-func (m *Message) TemplateVariables() map[string]any {
+func (m *CommonMessage) TemplateVariables() map[string]any {
return m.templateVariables
}
-func (m *Message) RecipientVariables() map[string]map[string]any {
+func (m *CommonMessage) RecipientVariables() map[string]map[string]any {
return m.recipientVariables
}
-func (m *Message) TemplateVersionTag() string {
+func (m *CommonMessage) TemplateVersionTag() string {
return m.templateVersionTag
}
-func (m *Message) TemplateRenderText() bool {
+func (m *CommonMessage) TemplateRenderText() bool {
return m.templateRenderText
}
-func (m *Message) RequireTLS() bool {
+func (m *CommonMessage) RequireTLS() bool {
return m.requireTLS
}
-func (m *Message) SkipVerification() bool {
+func (m *CommonMessage) SkipVerification() bool {
return m.skipVerification
}
// AddReaderAttachment arranges to send a file along with the e-mail message.
-// File contents are read from a io.ReadCloser.
+// File contents are read from an io.ReadCloser.
// The filename parameter is the resulting filename of the attachment.
-// The readCloser parameter is the io.ReadCloser which reads the actual bytes to be used
+// The readCloser parameter is the io.ReadCloser that reads the actual bytes to be used
// as the contents of the attached file.
-func (m *Message) AddReaderAttachment(filename string, readCloser io.ReadCloser) {
+func (m *CommonMessage) AddReaderAttachment(filename string, readCloser io.ReadCloser) {
ra := ReaderAttachment{Filename: filename, ReadCloser: readCloser}
m.readerAttachments = append(m.readerAttachments, ra)
}
@@ -340,7 +328,7 @@ func (m *Message) AddReaderAttachment(filename string, readCloser io.ReadCloser)
// The filename parameter is the resulting filename of the attachment.
// The buffer parameter is the []byte array which contains the actual bytes to be used
// as the contents of the attached file.
-func (m *Message) AddBufferAttachment(filename string, buffer []byte) {
+func (m *CommonMessage) AddBufferAttachment(filename string, buffer []byte) {
ba := BufferAttachment{Filename: filename, Buffer: buffer}
m.bufferAttachments = append(m.bufferAttachments, ba)
}
@@ -348,16 +336,16 @@ func (m *Message) AddBufferAttachment(filename string, buffer []byte) {
// AddAttachment arranges to send a file from the filesystem along with the e-mail message.
// The attachment parameter is a filename, which must refer to a file which actually resides
// in the local filesystem.
-func (m *Message) AddAttachment(attachment string) {
+func (m *CommonMessage) AddAttachment(attachment string) {
m.attachments = append(m.attachments, attachment)
}
// AddReaderInline arranges to send a file along with the e-mail message.
-// File contents are read from a io.ReadCloser.
+// File contents are read from an io.ReadCloser.
// The filename parameter is the resulting filename of the attachment.
-// The readCloser parameter is the io.ReadCloser which reads the actual bytes to be used
+// The readCloser parameter is the io.ReadCloser that reads the actual bytes to be used
// as the contents of the attached file.
-func (m *Message) AddReaderInline(filename string, readCloser io.ReadCloser) {
+func (m *CommonMessage) AddReaderInline(filename string, readCloser io.ReadCloser) {
ra := ReaderAttachment{Filename: filename, ReadCloser: readCloser}
m.readerInlines = append(m.readerInlines, ra)
}
@@ -367,20 +355,20 @@ func (m *Message) AddReaderInline(filename string, readCloser io.ReadCloser) {
// can be used to send image or font data along with an HTML-encoded message body.
// The attachment parameter is a filename, which must refer to a file which actually resides
// in the local filesystem.
-func (m *Message) AddInline(inline string) {
+func (m *CommonMessage) AddInline(inline string) {
m.inlines = append(m.inlines, inline)
}
// AddRecipient appends a receiver to the To: header of a message.
-// It will return an error if the limit of recipients have been exceeded for this message
-func (m *Message) AddRecipient(recipient string) error {
+// It will return an error if the limit of recipients has been exceeded for this message
+func (m *PlainMessage) AddRecipient(recipient string) error {
return m.AddRecipientAndVariables(recipient, nil)
}
// AddRecipientAndVariables appends a receiver to the To: header of a message,
// and as well attaches a set of variables relevant for this recipient.
-// It will return an error if the limit of recipients have been exceeded for this message
-func (m *Message) AddRecipientAndVariables(r string, vars map[string]any) error {
+// It will return an error if the limit of recipients has been exceeded for this message
+func (m *PlainMessage) AddRecipientAndVariables(r string, vars map[string]any) error {
if m.RecipientCount() >= MaxNumberOfRecipients {
return fmt.Errorf("recipient limit exceeded (max %d)", MaxNumberOfRecipients)
}
@@ -394,32 +382,25 @@ func (m *Message) AddRecipientAndVariables(r string, vars map[string]any) error
return nil
}
-// RecipientCount returns the total number of recipients for the message.
-// This includes To:, Cc:, and Bcc: fields.
-//
-// NOTE: At present, this method is reliable only for non-MIME messages, as the
-// Bcc: and Cc: fields are easily accessible.
-// For MIME messages, only the To: field is considered.
-// A fix for this issue is planned for a future release.
-// For now, MIME messages are always assumed to have 10 recipients between Cc: and Bcc: fields.
-// If your MIME messages have more than 10 non-To: field recipients,
-// you may find that some recipients will not receive your e-mail.
-// It's perfectly OK, of course, for a MIME message to not have any Cc: or Bcc: recipients.
-func (m *Message) RecipientCount() int {
- return len(m.To()) + m.Specific.RecipientCount()
+func (m *MimeMessage) AddRecipient(recipient string) error {
+ if m.RecipientCount() >= MaxNumberOfRecipients {
+ return fmt.Errorf("recipient limit exceeded (max %d)", MaxNumberOfRecipients)
+ }
+ m.to = append(m.to, recipient)
+
+ return nil
}
func (m *PlainMessage) RecipientCount() int {
- return len(m.BCC()) + len(m.CC())
+ return len(m.To()) + len(m.BCC()) + len(m.CC())
}
-func (*MimeMessage) RecipientCount() int {
- // TODO(v5): 10 + len(m.To())
- return 10
+func (m *MimeMessage) RecipientCount() int {
+ return 10 + len(m.To())
}
// SetReplyTo sets the receiver who should receive replies
-func (m *Message) SetReplyTo(recipient string) {
+func (m *CommonMessage) SetReplyTo(recipient string) {
m.AddHeader("Reply-To", recipient)
}
@@ -435,25 +416,12 @@ func (m *PlainMessage) AddBCC(r string) {
func (*MimeMessage) AddBCC(_ string) {}
-// Deprecated: use SetHTML instead.
-//
-// TODO(v5): remove this method
-func (m *Message) SetHtml(html string) {
- m.SetHTML(html)
-}
-
func (m *PlainMessage) SetHTML(h string) {
m.html = h
}
func (*MimeMessage) SetHTML(_ string) {}
-// Deprecated: use SetAmpHTML instead.
-// TODO(v5): remove this method
-func (m *Message) SetAMPHtml(html string) {
- m.SetAmpHTML(html)
-}
-
func (m *PlainMessage) SetAmpHTML(h string) {
m.ampHtml = h
}
@@ -462,7 +430,7 @@ func (*MimeMessage) SetAmpHTML(_ string) {}
// AddTag attaches tags to the message. Tags are useful for metrics gathering and event tracking purposes.
// Refer to the Mailgun documentation for further details.
-func (m *Message) AddTag(tag ...string) error {
+func (m *CommonMessage) AddTag(tag ...string) error {
if len(m.Tags()) >= MaxNumberOfTags {
return fmt.Errorf("cannot add any new tags. Message tag limit (%d) reached", MaxNumberOfTags)
}
@@ -477,34 +445,28 @@ func (m *PlainMessage) SetTemplate(t string) {
func (*MimeMessage) SetTemplate(_ string) {}
-// Deprecated: is no longer supported and is deprecated for new software.
-// TODO(v5): remove this method.
-func (m *Message) AddCampaign(campaign string) {
- m.campaigns = append(m.Campaigns(), campaign)
-}
-
// SetDKIM arranges to send the o:dkim header with the message, and sets its value accordingly.
// Refer to the Mailgun documentation for more information.
-func (m *Message) SetDKIM(dkim bool) {
+func (m *CommonMessage) SetDKIM(dkim bool) {
m.dkim = &dkim
}
-// EnableNativeSend allows the return path to match the address in the Message.Headers.From:
+// EnableNativeSend allows the return path to match the address in the CommonMessage.Headers.From:
// field when sending from Mailgun rather than the usual bounce+ address in the return path.
-func (m *Message) EnableNativeSend() {
+func (m *CommonMessage) EnableNativeSend() {
m.nativeSend = true
}
// EnableTestMode allows submittal of a message, such that it will be discarded by Mailgun.
// This facilitates testing client-side software without actually consuming e-mail resources.
-func (m *Message) EnableTestMode() {
+func (m *CommonMessage) EnableTestMode() {
m.testMode = true
}
// SetDeliveryTime schedules the message for transmission at the indicated time.
// Pass nil to remove any installed schedule.
// Refer to the Mailgun documentation for more information.
-func (m *Message) SetDeliveryTime(dt time.Time) {
+func (m *CommonMessage) SetDeliveryTime(dt time.Time) {
m.deliveryTime = dt
}
@@ -512,7 +474,7 @@ func (m *Message) SetDeliveryTime(dt time.Time) {
// String should be set to the number of hours in [0-9]+h format,
// with the minimum being 24h and the maximum being 72h.
// Refer to the Mailgun documentation for more information.
-func (m *Message) SetSTOPeriod(stoPeriod string) error {
+func (m *CommonMessage) SetSTOPeriod(stoPeriod string) error {
validPattern := `^([2-6][4-9]|[3-6][0-9]|7[0-2])h$`
// TODO(vtopc): regexp.Compile, which is called by regexp.MatchString, is a heave operation, move into global variable
// or just parse using time.ParseDuration().
@@ -536,17 +498,17 @@ func (m *Message) SetSTOPeriod(stoPeriod string) error {
// Its yes/no setting is determined by the call's parameter.
// Note that this header is not passed on to the final recipient(s).
// Refer to the Mailgun documentation for more information.
-func (m *Message) SetTracking(tracking bool) {
+func (m *CommonMessage) SetTracking(tracking bool) {
m.tracking = &tracking
}
// SetTrackingClicks information is found in the Mailgun documentation.
-func (m *Message) SetTrackingClicks(trackingClicks bool) {
+func (m *CommonMessage) SetTrackingClicks(trackingClicks bool) {
m.trackingClicks = ptr(yesNo(trackingClicks))
}
// SetTrackingOptions sets the o:tracking, o:tracking-clicks and o:tracking-opens at once.
-func (m *Message) SetTrackingOptions(options *TrackingOptions) {
+func (m *CommonMessage) SetTrackingOptions(options *TrackingOptions) {
m.tracking = &options.Tracking
m.trackingClicks = &options.TrackingClicks
@@ -555,32 +517,32 @@ func (m *Message) SetTrackingOptions(options *TrackingOptions) {
}
// SetRequireTLS information is found in the Mailgun documentation.
-func (m *Message) SetRequireTLS(b bool) {
+func (m *CommonMessage) SetRequireTLS(b bool) {
m.requireTLS = b
}
// SetSkipVerification information is found in the Mailgun documentation.
-func (m *Message) SetSkipVerification(b bool) {
+func (m *CommonMessage) SetSkipVerification(b bool) {
m.skipVerification = b
}
// SetTrackingOpens information is found in the Mailgun documentation.
-func (m *Message) SetTrackingOpens(trackingOpens bool) {
+func (m *CommonMessage) SetTrackingOpens(trackingOpens bool) {
m.trackingOpens = &trackingOpens
}
// SetTemplateVersion information is found in the Mailgun documentation.
-func (m *Message) SetTemplateVersion(tag string) {
+func (m *CommonMessage) SetTemplateVersion(tag string) {
m.templateVersionTag = tag
}
// SetTemplateRenderText information is found in the Mailgun documentation.
-func (m *Message) SetTemplateRenderText(render bool) {
+func (m *CommonMessage) SetTemplateRenderText(render bool) {
m.templateRenderText = render
}
// AddHeader allows you to send custom MIME headers with the message.
-func (m *Message) AddHeader(header, value string) {
+func (m *CommonMessage) AddHeader(header, value string) {
if m.headers == nil {
m.headers = make(map[string]string)
}
@@ -590,7 +552,7 @@ func (m *Message) AddHeader(header, value string) {
// AddVariable lets you associate a set of variables with messages you send,
// which Mailgun can use to, in essence, complete form-mail.
// Refer to the Mailgun documentation for more information.
-func (m *Message) AddVariable(variable string, value any) error {
+func (m *CommonMessage) AddVariable(variable string, value any) error {
if m.variables == nil {
m.variables = make(map[string]string)
}
@@ -613,7 +575,7 @@ func (m *Message) AddVariable(variable string, value any) error {
// AddTemplateVariable adds a template variable to the map of template variables, replacing the variable if it is already there.
// This is used for server-side message templates and can nest arbitrary values. At send time, the resulting map will be converted into
// a JSON string and sent as a header in the X-Mailgun-Variables header.
-func (m *Message) AddTemplateVariable(variable string, value any) error {
+func (m *CommonMessage) AddTemplateVariable(variable string, value any) error {
if m.templateVariables == nil {
m.templateVariables = make(map[string]any)
}
@@ -622,31 +584,22 @@ func (m *Message) AddTemplateVariable(variable string, value any) error {
}
// AddDomain allows you to use a separate domain for the type of messages you are sending.
-func (m *Message) AddDomain(domain string) {
+func (m *CommonMessage) AddDomain(domain string) {
m.domain = domain
}
-// Headers retrieves the http headers associated with this message
-func (m *Message) Headers() map[string]string {
- return m.headers
-}
-
-// Deprecated: use func Headers() instead.
-// TODO(v5): remove this method, it violates https://go.dev/doc/effective_go#Getters
-func (m *Message) GetHeaders() map[string]string {
+// Headers retrieve the http headers associated with this message
+func (m *CommonMessage) Headers() map[string]string {
return m.headers
}
-// ErrInvalidMessage is returned by `Send()` when the `mailgun.Message` struct is incomplete
+// ErrInvalidMessage is returned by `Send()` when the `mailgun.CommonMessage` struct is incomplete
var ErrInvalidMessage = errors.New("message not valid")
type SendableMessage interface {
Domain() string
To() []string
Tags() []string
- // Deprecated: is no longer supported and is deprecated for new software.
- // TODO(v5): remove this method
- Campaigns() []string
DKIM() *bool
DeliveryTime() time.Time
STOPeriod() string
@@ -672,12 +625,12 @@ type SendableMessage interface {
Specific
}
-// Send attempts to queue a message (see Message, NewMessage, and its methods) for delivery.
+// Send attempts to queue a message (see PlainMessage, MimeMessage and its methods) for delivery.
// It returns the Mailgun server response, which consists of two components:
// - A human-readable status message, typically "Queued. Thank you."
// - A Message ID, which is the id used to track the queued message. The message id is useful
// when contacting support to report an issue with a specific message or to relate a
-// delivered, accepted or failed event back to specific message.
+// delivered, accepted or failed event back to a specific message.
//
// The status and message ID are only returned if no error occurred.
//
@@ -693,15 +646,14 @@ type SendableMessage interface {
// }
//
// See the public mailgun documentation for all possible return codes and error messages
-// TODO(v5): switch m to SendableMessage interface - https://bryanftan.medium.com/accept-interfaces-return-structs-in-go-d4cab29a301b
-func (mg *MailgunImpl) Send(ctx context.Context, m *Message) (mes, id string, err error) {
- if mg.domain == "" {
+func (mg *MailgunImpl) Send(ctx context.Context, m SendableMessage) (mes, id string, err error) {
+ if m.Domain() == "" {
err = errors.New("you must provide a valid domain before calling Send()")
return "", "", err
}
invalidChars := ":&'@(),!?#;%+=<>"
- if i := strings.ContainsAny(mg.domain, invalidChars); i {
+ if i := strings.ContainsAny(m.Domain(), invalidChars); i {
err = fmt.Errorf("you called Send() with a domain that contains invalid characters")
return "", "", err
}
@@ -722,18 +674,14 @@ func (mg *MailgunImpl) Send(ctx context.Context, m *Message) (mes, id string, er
}
payload := NewFormDataPayload()
- m.Specific.AddValues(payload)
+ m.AddValues(payload)
+ // TODO: make (CommonMessage).AddValues()?
err = addMessageValues(payload, m)
if err != nil {
return "", "", err
}
- // TODO(v5): remove due for domain agnostic API:
- if m.Domain() == "" {
- m.domain = mg.Domain()
- }
-
r := newHTTPRequest(generateApiUrlWithDomain(mg, m.Endpoint(), m.Domain()))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
@@ -782,10 +730,6 @@ func addMessageOptions(dst *FormDataPayload, src SendableMessage) {
for _, tag := range src.Tags() {
dst.addValue("o:tag", tag)
}
- for _, campaign := range src.Campaigns() {
- // TODO(v5): deprecated - https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Messages/
- dst.addValue("o:campaign", campaign)
- }
if src.DKIM() != nil {
dst.addValue("o:dkim", yesNo(*src.DKIM()))
}
@@ -842,7 +786,7 @@ func addMessageVariables(dst *FormDataPayload, src SendableMessage) error {
if src.TemplateVariables() != nil {
variableString, err := json.Marshal(src.TemplateVariables())
if err == nil {
- // the map was marshalled as json so add it
+ // the map was marshaled as JSON so add it
dst.addValue("h:X-Mailgun-Variables", string(variableString))
}
}
@@ -931,7 +875,7 @@ func trueFalse(b bool) string {
}
// isValid returns true if, and only if,
-// a Message instance is sufficiently initialized to send via the Mailgun interface.
+// a CommonMessage instance is sufficiently initialized to send via the Mailgun interface.
func isValid(m SendableMessage) bool {
if m == nil {
return false
@@ -949,10 +893,6 @@ func isValid(m SendableMessage) bool {
return false
}
- if !validateStringList(m.Campaigns(), false) || len(m.Campaigns()) > 3 {
- return false
- }
-
return true
}
diff --git a/messages_test.go b/messages_test.go
index 408297e7..8b2f1ae7 100644
--- a/messages_test.go
+++ b/messages_test.go
@@ -242,7 +242,7 @@ func TestSendMGMIME(t *testing.T) {
require.NoError(t, err)
ctx := context.Background()
- m := mailgun.NewMIMEMessage(io.NopCloser(strings.NewReader(exampleMime)), toUser)
+ m := mailgun.NewMIMEMessage(os.Getenv("MG_DOMAIN"), io.NopCloser(strings.NewReader(exampleMime)), toUser)
msg, id, err := mg.Send(ctx, m)
require.NoError(t, err)
t.Log("TestSendMIME:MSG(" + msg + "),ID(" + id + ")")
@@ -257,7 +257,7 @@ func TestSendMGBatchFailRecipients(t *testing.T) {
spendMoney(t, func() {
toUser := os.Getenv("MG_EMAIL_TO")
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText+"Batch\n")
+ m := mailgun.NewMessage(os.Getenv("MG_DOMAIN"), fromUser, exampleSubject, exampleText+"Batch\n")
for i := 0; i < mailgun.MaxNumberOfRecipients; i++ {
err := m.AddRecipient("") // We expect this to indicate a failure at the API
require.NoError(t, err)
@@ -280,7 +280,7 @@ func TestSendMGBatchRecipientVariables(t *testing.T) {
require.NoError(t, err)
ctx := context.Background()
- m := mailgun.NewMessage(fromUser, exampleSubject, templateText)
+ m := mailgun.NewMessage(os.Getenv("MG_DOMAIN"), fromUser, exampleSubject, templateText)
err = m.AddRecipientAndVariables(toUser, map[string]any{
"name": "Joe Cool Example",
"table": 42,
@@ -315,7 +315,7 @@ func TestSendMGOffline(t *testing.T) {
mg.SetAPIBase(srv.URL + "/v3")
ctx := context.Background()
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText, toUser)
msg, id, err := mg.Send(ctx, m)
require.NoError(t, err)
assert.Equal(t, exampleMessage, msg)
@@ -348,7 +348,7 @@ func TestSendMGSeparateDomain(t *testing.T) {
mg.SetAPIBase(srv.URL + "/v3")
ctx := context.Background()
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText, toUser)
m.AddDomain(signingDomain)
msg, id, err := mg.Send(ctx, m)
@@ -401,7 +401,7 @@ func TestSendMGMessageVariables(t *testing.T) {
mg := mailgun.NewMailgun(exampleDomain, exampleAPIKey)
mg.SetAPIBase(srv.URL + "/v3")
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText, toUser)
err := m.AddVariable(exampleStrVarKey, exampleStrVarVal)
require.NoError(t, err)
err = m.AddVariable(exampleBoolVarKey, false)
@@ -418,7 +418,7 @@ func TestSendMGMessageVariables(t *testing.T) {
}
func TestAddRecipientsError(t *testing.T) {
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText)
+ m := mailgun.NewMessage(domain, fromUser, exampleSubject, exampleText)
for i := 0; i < 1000; i++ {
recipient := fmt.Sprintf("recipient_%d@example.com", i)
@@ -433,7 +433,7 @@ func TestAddRecipientsError(t *testing.T) {
func TestAddRecipientAndVariablesError(t *testing.T) {
var err error
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText)
+ m := mailgun.NewMessage(domain, fromUser, exampleSubject, exampleText)
for i := 0; i < 1000; i++ {
recipient := fmt.Sprintf("recipient_%d@example.com", i)
@@ -472,7 +472,7 @@ func TestSendDomainError(t *testing.T) {
ctx := context.Background()
mg := mailgun.NewMailgun(c.domain, exampleAPIKey)
mg.SetAPIBase(srv.URL + "/v3")
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, "test@test.com")
+ m := mailgun.NewMessage(c.domain, fromUser, exampleSubject, exampleText, "test@test.com")
_, _, err := mg.Send(ctx, m)
if c.isValid {
@@ -498,7 +498,7 @@ func TestSendEOFError(t *testing.T) {
mg := mailgun.NewMailgun(exampleDomain, exampleAPIKey)
mg.SetAPIBase(srv.URL + "/v3")
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText, toUser)
_, _, err := mg.Send(context.Background(), m)
require.NotNil(t, err)
// TODO(vtopc): do not compare strings, use errors.Is or errors.As:
@@ -524,18 +524,18 @@ func TestHasRecipient(t *testing.T) {
mg.SetAPIBase(srv.URL + "/v3")
// No recipient
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText)
_, _, err := mg.Send(context.Background(), m)
require.EqualError(t, err, "message not valid")
// Provided Bcc
- m = mailgun.NewMessage(fromUser, exampleSubject, exampleText)
+ m = mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText)
m.AddBCC(recipient)
_, _, err = mg.Send(context.Background(), m)
require.NoError(t, err)
// Provided cc
- m = mailgun.NewMessage(fromUser, exampleSubject, exampleText)
+ m = mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText)
m.AddCC(recipient)
_, _, err = mg.Send(context.Background(), m)
require.NoError(t, err)
@@ -597,7 +597,7 @@ func TestAddOverrideHeader(t *testing.T) {
mg.AddOverrideHeader("CustomHeader", "custom-value")
ctx := context.Background()
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText, toUser)
m.SetRequireTLS(true)
m.SetSkipVerification(true)
@@ -636,7 +636,7 @@ func TestOnBehalfOfSubaccount(t *testing.T) {
mg.SetOnBehalfOfSubaccount("mailgun.subaccount")
ctx := context.Background()
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText, toUser)
m.SetRequireTLS(true)
m.SetSkipVerification(true)
@@ -668,7 +668,7 @@ func TestCaptureCurlOutput(t *testing.T) {
mg.SetAPIBase(srv.URL + "/v3")
ctx := context.Background()
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText, toUser)
msg, id, err := mg.Send(ctx, m)
require.NoError(t, err)
assert.Equal(t, exampleMessage, msg)
@@ -704,7 +704,7 @@ func TestSendTLSOptions(t *testing.T) {
mg.SetAPIBase(srv.URL + "/v3")
ctx := context.Background()
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, exampleText, toUser)
m.SetRequireTLS(true)
m.SetSkipVerification(true)
@@ -734,7 +734,7 @@ func TestSendTemplate(t *testing.T) {
mg.SetAPIBase(srv.URL + "/v3")
ctx := context.Background()
- m := mailgun.NewMessage(fromUser, exampleSubject, "", toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, "", toUser)
m.SetTemplate(templateName)
msg, id, err := mg.Send(ctx, m)
@@ -767,7 +767,7 @@ func TestSendTemplateOptions(t *testing.T) {
mg.SetAPIBase(srv.URL + "/v3")
ctx := context.Background()
- m := mailgun.NewMessage(fromUser, exampleSubject, "", toUser)
+ m := mailgun.NewMessage(exampleDomain, fromUser, exampleSubject, "", toUser)
m.SetTemplate(templateName)
m.SetTemplateRenderText(true)
m.SetTemplateVersion(templateVersionTag)
@@ -779,7 +779,7 @@ func TestSendTemplateOptions(t *testing.T) {
}
func TestSendableMessageIface(t *testing.T) {
- m := mailgun.NewMessage(fromUser, exampleSubject, exampleText)
+ m := mailgun.NewMessage(domain, fromUser, exampleSubject, exampleText)
assert.Implements(t, (*mailgun.SendableMessage)(nil), m)
}
diff --git a/messages_types_v5.go b/messages_types_v5.go
index 0cc843f0..92139081 100644
--- a/messages_types_v5.go
+++ b/messages_types_v5.go
@@ -1,13 +1,14 @@
package mailgun
// This file contains a draft for new v5 messages.
+// TODO(v5): remove this file
import (
"io"
"time"
)
-// Message structures contain both the message text and the envelope for an e-mail message.
+// CommonMessage structures contain both the message text and the envelope for an e-mail message.
// TODO(v5): rename to CommonMessage
type commonMessageV5 struct {
domain string
diff --git a/messages_v5.go b/messages_v5.go
index 23cca5e9..4342ed7a 100644
--- a/messages_v5.go
+++ b/messages_v5.go
@@ -1,6 +1,7 @@
package mailgun
// This file contains methods for new v5 messages.
+// TODO(v5): remove this file
import (
"context"
@@ -119,7 +120,7 @@ func (m *commonMessageV5) SetDKIM(dkim bool) {
m.dkim = &dkim
}
-// EnableNativeSend allows the return path to match the address in the Message.Headers.From:
+// EnableNativeSend allows the return path to match the address in the CommonMessage.Headers.From:
// field when sending from Mailgun rather than the usual bounce+ address in the return path.
func (m *commonMessageV5) EnableNativeSend() {
m.nativeSend = true
@@ -381,7 +382,7 @@ func (*mimeMessageV5) Endpoint() string {
return mimeMessagesEndpoint
}
-// Send attempts to queue a message (see Message, NewMessage, and its methods) for delivery.
+// Send attempts to queue a message (see CommonMessage, NewMessage, and its methods) for delivery.
// It returns the Mailgun server response, which consists of two components:
// - A human-readable status message, typically "Queued. Thank you."
// - A Message ID, which is the id used to track the queued message. The message id is useful
diff --git a/storage_test.go b/storage_test.go
index e22434e1..33a0df2c 100644
--- a/storage_test.go
+++ b/storage_test.go
@@ -18,7 +18,7 @@ func TestStorage(t *testing.T) {
var ctx = context.Background()
- m := mailgun.NewMessage("root@"+testDomain, "Subject", "Text Body", "stored@"+testDomain)
+ m := mailgun.NewMessage(testDomain, "root@"+testDomain, "Subject", "Text Body", "stored@"+testDomain)
msg, id, err := mg.Send(ctx, m)
require.NoError(t, err)
diff --git a/tags_test.go b/tags_test.go
index 1e642529..e7cf3145 100644
--- a/tags_test.go
+++ b/tags_test.go
@@ -20,7 +20,7 @@ const (
func TestTags(t *testing.T) {
mg := mailgun.NewMailgun(testDomain, testKey)
mg.SetAPIBase(server.URL())
- msg := mailgun.NewMessage(fromUser, exampleSubject, exampleText, "test@example.com")
+ msg := mailgun.NewMessage(testDomain, fromUser, exampleSubject, exampleText, "test@example.com")
require.NoError(t, msg.AddTag("newsletter"))
require.NoError(t, msg.AddTag("homer"))
require.NoError(t, msg.AddTag("bart"))