Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: added Send Time Optimization (STO) param #304

Merged
merged 1 commit into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 47 additions & 18 deletions messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"time"
Expand All @@ -25,6 +26,7 @@ type Message struct {
campaigns []string
dkim bool
deliveryTime time.Time
stoPeriod string
attachments []string
readerAttachments []ReaderAttachment
inlines []string
Expand Down Expand Up @@ -169,8 +171,8 @@ type features interface {
// Pass nil as the to parameter to skip adding the To: header at this stage.
// You can do this explicitly, or implicitly, as follows:
//
// // Note absence of To parameter(s)!
// m := mg.NewMessage("[email protected]", "Help save our planet", "Hello world!")
// // Note absence of To parameter(s)!
// m := mg.NewMessage("[email protected]", "Help save our planet", "Hello world!")
//
// Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method
// before sending, though.
Expand Down Expand Up @@ -198,8 +200,8 @@ func (mg *MailgunImpl) NewMessage(from, subject, text string, to ...string) *Mes
// Pass nil as the to parameter to skip adding the To: header at this stage.
// You can do this explicitly, or implicitly, as follows:
//
// // Note absence of To parameter(s)!
// m := mg.NewMessage("[email protected]", "Help save our planet", "Hello world!")
// // Note absence of To parameter(s)!
// m := mg.NewMessage("[email protected]", "Help save our planet", "Hello world!")
//
// Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method
// before sending, though.
Expand Down Expand Up @@ -414,6 +416,25 @@ func (m *Message) SetDeliveryTime(dt time.Time) {
m.deliveryTime = dt
}

// SetSTOPeriod toggles Send Time Optimization (STO) on a per-message basis.
// 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 {
validPattern := `^([2-6][4-9]|[3-6][0-9]|7[0-2])h$`
match, err := regexp.MatchString(validPattern, stoPeriod)
if err != nil {
return err
}

if !match {
return errors.New("STO period is invalid. Valid range is 24h to 72h")
}

m.stoPeriod = stoPeriod
return nil
}

// SetTracking sets the o:tracking message parameter to adjust, on a message-by-message basis,
// whether or not Mailgun will rewrite URLs to facilitate event tracking.
// Events tracked includes opens, clicks, unsubscribes, etc.
Expand Down Expand Up @@ -454,18 +475,18 @@ func (m *Message) SetSkipVerification(b bool) {
m.skipVerification = b
}

//SetTrackingOpens information is found in the Mailgun documentation.
// SetTrackingOpens information is found in the Mailgun documentation.
func (m *Message) SetTrackingOpens(trackingOpens bool) {
m.trackingOpens = trackingOpens
m.trackingOpensSet = true
}

//SetTemplateVersion information is found in the Mailgun documentation.
// SetTemplateVersion information is found in the Mailgun documentation.
func (m *Message) SetTemplateVersion(tag string) {
m.templateVersionTag = tag
}

//SetTemplateRenderText information is found in the Mailgun documentation.
// SetTemplateRenderText information is found in the Mailgun documentation.
func (m *Message) SetTemplateRenderText(render bool) {
m.templateRenderText = render
}
Expand Down Expand Up @@ -527,25 +548,25 @@ var ErrInvalidMessage = errors.New("message not valid")

// Send attempts to queue a message (see Message, 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
// when contacting support to report an issue with a specific message or to relate a
// delivered, accepted or failed event back to specific message.
// - 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.
//
// The status and message ID are only returned if no error occurred.
//
// Error returns can be of type `error.Error` which wrap internal and standard
// golang errors like `url.Error`. The error can also be of type
// mailgun.UnexpectedResponseError which contains the error returned by the mailgun API.
//
// mailgun.UnexpectedResponseError {
// URL: "https://api.mailgun.com/v3/messages",
// Expected: 200,
// Actual: 400,
// Data: "Domain not found: example.com",
// }
// mailgun.UnexpectedResponseError {
// URL: "https://api.mailgun.com/v3/messages",
// Expected: 200,
// Actual: 400,
// Data: "Domain not found: example.com",
// }
//
// See the public mailgun documentation for all possible return codes and error messages
// See the public mailgun documentation for all possible return codes and error messages
func (mg *MailgunImpl) Send(ctx context.Context, message *Message) (mes string, id string, err error) {
if mg.domain == "" {
err = errors.New("you must provide a valid domain before calling Send()")
Expand All @@ -567,6 +588,11 @@ func (mg *MailgunImpl) Send(ctx context.Context, message *Message) (mes string,
err = ErrInvalidMessage
return
}

if message.stoPeriod != "" && message.RecipientCount() > 1 {
err = errors.New("STO can only be used on a per-message basis")
return
}
payload := newFormDataPayload()

message.specific.addValues(payload)
Expand All @@ -585,6 +611,9 @@ func (mg *MailgunImpl) Send(ctx context.Context, message *Message) (mes string,
if !message.deliveryTime.IsZero() {
payload.addValue("o:deliverytime", formatMailgunTime(message.deliveryTime))
}
if message.stoPeriod != "" {
payload.addValue("o:deliverytime-optimize-period", message.stoPeriod)
}
if message.nativeSend {
payload.addValue("o:native-send", "yes")
}
Expand Down
19 changes: 19 additions & 0 deletions messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,25 @@ func TestSendMGPlainAt(t *testing.T) {
})
}

func TestSendMGSTO(t *testing.T) {
if reason := mailgun.SkipNetworkTest(); reason != "" {
t.Skip(reason)
}

spendMoney(t, func() {
toUser := os.Getenv("MG_EMAIL_TO")
mg, err := mailgun.NewMailgunFromEnv()
ensure.Nil(t, err)

ctx := context.Background()
m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
m.SetSTOPeriod("24h")
msg, id, err := mg.Send(ctx, m)
ensure.Nil(t, err)
t.Log("TestSendMGSTO:MSG(" + msg + "),ID(" + id + ")")
})
}

func TestSendMGHtml(t *testing.T) {
if reason := mailgun.SkipNetworkTest(); reason != "" {
t.Skip(reason)
Expand Down