Skip to content

Commit

Permalink
Merge pull request #304 from AmazingTalker/feat/add_sto_param
Browse files Browse the repository at this point in the history
feat: added Send Time Optimization (STO) param
  • Loading branch information
thrawn01 committed Jun 6, 2023
2 parents e4cf7a4 + a720bfc commit dd31e58
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 18 deletions.
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

0 comments on commit dd31e58

Please sign in to comment.