github.com/mailgun/mailgun-go/v3@v3.6.4/messages.go (about) 1 package mailgun 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "strconv" 10 "time" 11 ) 12 13 // MaxNumberOfRecipients represents the largest batch of recipients that Mailgun can support in a single API call. 14 // This figure includes To:, Cc:, Bcc:, etc. recipients. 15 const MaxNumberOfRecipients = 1000 16 17 // MaxNumberOfTags represents the maximum number of tags that can be added for a message 18 const MaxNumberOfTags = 3 19 20 // Message structures contain both the message text and the envelop for an e-mail message. 21 type Message struct { 22 to []string 23 tags []string 24 campaigns []string 25 dkim bool 26 deliveryTime time.Time 27 attachments []string 28 readerAttachments []ReaderAttachment 29 inlines []string 30 readerInlines []ReaderAttachment 31 bufferAttachments []BufferAttachment 32 33 nativeSend bool 34 testMode bool 35 tracking bool 36 trackingClicks bool 37 trackingOpens bool 38 headers map[string]string 39 variables map[string]string 40 templateVariables map[string]interface{} 41 recipientVariables map[string]map[string]interface{} 42 domain string 43 44 dkimSet bool 45 trackingSet bool 46 trackingClicksSet bool 47 trackingOpensSet bool 48 requireTLS bool 49 skipVerification bool 50 51 specific features 52 mg Mailgun 53 } 54 55 type ReaderAttachment struct { 56 Filename string 57 ReadCloser io.ReadCloser 58 } 59 60 type BufferAttachment struct { 61 Filename string 62 Buffer []byte 63 } 64 65 // StoredMessage structures contain the (parsed) message content for an email 66 // sent to a Mailgun account. 67 // 68 // The MessageHeaders field is special, in that it's formatted as a slice of pairs. 69 // Each pair consists of a name [0] and value [1]. Array notation is used instead of a map 70 // because that's how it's sent over the wire, and it's how encoding/json expects this field 71 // to be. 72 type StoredMessage struct { 73 Recipients string `json:"recipients"` 74 Sender string `json:"sender"` 75 From string `json:"from"` 76 Subject string `json:"subject"` 77 BodyPlain string `json:"body-plain"` 78 StrippedText string `json:"stripped-text"` 79 StrippedSignature string `json:"stripped-signature"` 80 BodyHtml string `json:"body-html"` 81 StrippedHtml string `json:"stripped-html"` 82 Attachments []StoredAttachment `json:"attachments"` 83 MessageUrl string `json:"message-url"` 84 ContentIDMap map[string]struct { 85 Url string `json:"url"` 86 ContentType string `json:"content-type"` 87 Name string `json:"name"` 88 Size int64 `json:"size"` 89 } `json:"content-id-map"` 90 MessageHeaders [][]string `json:"message-headers"` 91 } 92 93 // StoredAttachment structures contain information on an attachment associated with a stored message. 94 type StoredAttachment struct { 95 Size int `json:"size"` 96 Url string `json:"url"` 97 Name string `json:"name"` 98 ContentType string `json:"content-type"` 99 } 100 101 type StoredMessageRaw struct { 102 Recipients string `json:"recipients"` 103 Sender string `json:"sender"` 104 From string `json:"from"` 105 Subject string `json:"subject"` 106 BodyMime string `json:"body-mime"` 107 } 108 109 // plainMessage contains fields relevant to plain API-synthesized messages. 110 // You're expected to use various setters to set most of these attributes, 111 // although from, subject, and text are set when the message is created with 112 // NewMessage. 113 type plainMessage struct { 114 from string 115 cc []string 116 bcc []string 117 subject string 118 text string 119 html string 120 template string 121 } 122 123 // mimeMessage contains fields relevant to pre-packaged MIME messages. 124 type mimeMessage struct { 125 body io.ReadCloser 126 } 127 128 type sendMessageResponse struct { 129 Message string `json:"message"` 130 Id string `json:"id"` 131 } 132 133 // features abstracts the common characteristics between regular and MIME messages. 134 // addCC, addBCC, recipientCount, and setHTML are invoked via the package-global AddCC, AddBCC, 135 // RecipientCount, and SetHtml calls, as these functions are ignored for MIME messages. 136 // Send() invokes addValues to add message-type-specific MIME headers for the API call 137 // to Mailgun. isValid yeilds true if and only if the message is valid enough for sending 138 // through the API. Finally, endpoint() tells Send() which endpoint to use to submit the API call. 139 type features interface { 140 addCC(string) 141 addBCC(string) 142 setHtml(string) 143 addValues(*formDataPayload) 144 isValid() bool 145 endpoint() string 146 recipientCount() int 147 setTemplate(string) 148 } 149 150 // NewMessage returns a new e-mail message with the simplest envelop needed to send. 151 // 152 // Unlike the global function, 153 // this method supports arbitrary-sized recipient lists by 154 // automatically sending mail in batches of up to MaxNumberOfRecipients. 155 // 156 // To support batch sending, you don't want to provide a fixed To: header at this point. 157 // Pass nil as the to parameter to skip adding the To: header at this stage. 158 // You can do this explicitly, or implicitly, as follows: 159 // 160 // // Note absence of To parameter(s)! 161 // m := mg.NewMessage("me@example.com", "Help save our planet", "Hello world!") 162 // 163 // Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method 164 // before sending, though. 165 func (mg *MailgunImpl) NewMessage(from, subject, text string, to ...string) *Message { 166 return &Message{ 167 specific: &plainMessage{ 168 from: from, 169 subject: subject, 170 text: text, 171 }, 172 to: to, 173 mg: mg, 174 } 175 } 176 177 // NewMIMEMessage creates a new MIME message. These messages are largely canned; 178 // you do not need to invoke setters to set message-related headers. 179 // However, you do still need to call setters for Mailgun-specific settings. 180 // 181 // Unlike the global function, 182 // this method supports arbitrary-sized recipient lists by 183 // automatically sending mail in batches of up to MaxNumberOfRecipients. 184 // 185 // To support batch sending, you don't want to provide a fixed To: header at this point. 186 // Pass nil as the to parameter to skip adding the To: header at this stage. 187 // You can do this explicitly, or implicitly, as follows: 188 // 189 // // Note absence of To parameter(s)! 190 // m := mg.NewMessage("me@example.com", "Help save our planet", "Hello world!") 191 // 192 // Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method 193 // before sending, though. 194 func (mg *MailgunImpl) NewMIMEMessage(body io.ReadCloser, to ...string) *Message { 195 return &Message{ 196 specific: &mimeMessage{ 197 body: body, 198 }, 199 to: to, 200 mg: mg, 201 } 202 } 203 204 // AddReaderAttachment arranges to send a file along with the e-mail message. 205 // File contents are read from a io.ReadCloser. 206 // The filename parameter is the resulting filename of the attachment. 207 // The readCloser parameter is the io.ReadCloser which reads the actual bytes to be used 208 // as the contents of the attached file. 209 func (m *Message) AddReaderAttachment(filename string, readCloser io.ReadCloser) { 210 ra := ReaderAttachment{Filename: filename, ReadCloser: readCloser} 211 m.readerAttachments = append(m.readerAttachments, ra) 212 } 213 214 // AddBufferAttachment arranges to send a file along with the e-mail message. 215 // File contents are read from the []byte array provided 216 // The filename parameter is the resulting filename of the attachment. 217 // The buffer parameter is the []byte array which contains the actual bytes to be used 218 // as the contents of the attached file. 219 func (m *Message) AddBufferAttachment(filename string, buffer []byte) { 220 ba := BufferAttachment{Filename: filename, Buffer: buffer} 221 m.bufferAttachments = append(m.bufferAttachments, ba) 222 } 223 224 // AddAttachment arranges to send a file from the filesystem along with the e-mail message. 225 // The attachment parameter is a filename, which must refer to a file which actually resides 226 // in the local filesystem. 227 func (m *Message) AddAttachment(attachment string) { 228 m.attachments = append(m.attachments, attachment) 229 } 230 231 // AddReaderInline arranges to send a file along with the e-mail message. 232 // File contents are read from a io.ReadCloser. 233 // The filename parameter is the resulting filename of the attachment. 234 // The readCloser parameter is the io.ReadCloser which reads the actual bytes to be used 235 // as the contents of the attached file. 236 func (m *Message) AddReaderInline(filename string, readCloser io.ReadCloser) { 237 ra := ReaderAttachment{Filename: filename, ReadCloser: readCloser} 238 m.readerInlines = append(m.readerInlines, ra) 239 } 240 241 // AddInline arranges to send a file along with the e-mail message, but does so 242 // in a way that its data remains "inline" with the rest of the message. This 243 // can be used to send image or font data along with an HTML-encoded message body. 244 // The attachment parameter is a filename, which must refer to a file which actually resides 245 // in the local filesystem. 246 func (m *Message) AddInline(inline string) { 247 m.inlines = append(m.inlines, inline) 248 } 249 250 // AddRecipient appends a receiver to the To: header of a message. 251 // It will return an error if the limit of recipients have been exceeded for this message 252 func (m *Message) AddRecipient(recipient string) error { 253 return m.AddRecipientAndVariables(recipient, nil) 254 } 255 256 // AddRecipientAndVariables appends a receiver to the To: header of a message, 257 // and as well attaches a set of variables relevant for this recipient. 258 // It will return an error if the limit of recipients have been exceeded for this message 259 func (m *Message) AddRecipientAndVariables(r string, vars map[string]interface{}) error { 260 if m.RecipientCount() >= MaxNumberOfRecipients { 261 return fmt.Errorf("recipient limit exceeded (max %d)", MaxNumberOfRecipients) 262 } 263 m.to = append(m.to, r) 264 if vars != nil { 265 if m.recipientVariables == nil { 266 m.recipientVariables = make(map[string]map[string]interface{}) 267 } 268 m.recipientVariables[r] = vars 269 } 270 return nil 271 } 272 273 // RecipientCount returns the total number of recipients for the message. 274 // This includes To:, Cc:, and Bcc: fields. 275 // 276 // NOTE: At present, this method is reliable only for non-MIME messages, as the 277 // Bcc: and Cc: fields are easily accessible. 278 // For MIME messages, only the To: field is considered. 279 // A fix for this issue is planned for a future release. 280 // For now, MIME messages are always assumed to have 10 recipients between Cc: and Bcc: fields. 281 // If your MIME messages have more than 10 non-To: field recipients, 282 // you may find that some recipients will not receive your e-mail. 283 // It's perfectly OK, of course, for a MIME message to not have any Cc: or Bcc: recipients. 284 func (m *Message) RecipientCount() int { 285 return len(m.to) + m.specific.recipientCount() 286 } 287 288 func (pm *plainMessage) recipientCount() int { 289 return len(pm.bcc) + len(pm.cc) 290 } 291 292 func (mm *mimeMessage) recipientCount() int { 293 return 10 294 } 295 296 func (m *Message) send(ctx context.Context) (string, string, error) { 297 return m.mg.Send(ctx, m) 298 } 299 300 // SetReplyTo sets the receiver who should receive replies 301 func (m *Message) SetReplyTo(recipient string) { 302 m.AddHeader("Reply-To", recipient) 303 } 304 305 // AddCC appends a receiver to the carbon-copy header of a message. 306 func (m *Message) AddCC(recipient string) { 307 m.specific.addCC(recipient) 308 } 309 310 func (pm *plainMessage) addCC(r string) { 311 pm.cc = append(pm.cc, r) 312 } 313 314 func (mm *mimeMessage) addCC(_ string) {} 315 316 // AddBCC appends a receiver to the blind-carbon-copy header of a message. 317 func (m *Message) AddBCC(recipient string) { 318 m.specific.addBCC(recipient) 319 } 320 321 func (pm *plainMessage) addBCC(r string) { 322 pm.bcc = append(pm.bcc, r) 323 } 324 325 func (mm *mimeMessage) addBCC(_ string) {} 326 327 // SetHtml is a helper. If you're sending a message that isn't already MIME encoded, SetHtml() will arrange to bundle 328 // an HTML representation of your message in addition to your plain-text body. 329 func (m *Message) SetHtml(html string) { 330 m.specific.setHtml(html) 331 } 332 333 func (pm *plainMessage) setHtml(h string) { 334 pm.html = h 335 } 336 337 func (mm *mimeMessage) setHtml(_ string) {} 338 339 // AddTag attaches tags to the message. Tags are useful for metrics gathering and event tracking purposes. 340 // Refer to the Mailgun documentation for further details. 341 func (m *Message) AddTag(tag ...string) error { 342 if len(m.tags) >= MaxNumberOfTags { 343 return fmt.Errorf("cannot add any new tags. Message tag limit (%d) reached", MaxNumberOfTags) 344 } 345 346 m.tags = append(m.tags, tag...) 347 return nil 348 } 349 350 // SetTemplate sets the name of a template stored via the template API. 351 // See https://documentation.mailgun.com/en/latest/user_manual.html#templating 352 func (m *Message) SetTemplate(t string) { 353 m.specific.setTemplate(t) 354 } 355 356 func (pm *plainMessage) setTemplate(t string) { 357 pm.template = t 358 } 359 360 func (mm *mimeMessage) setTemplate(t string) {} 361 362 // AddCampaign is no longer supported and is deprecated for new software. 363 func (m *Message) AddCampaign(campaign string) { 364 m.campaigns = append(m.campaigns, campaign) 365 } 366 367 // SetDKIM arranges to send the o:dkim header with the message, and sets its value accordingly. 368 // Refer to the Mailgun documentation for more information. 369 func (m *Message) SetDKIM(dkim bool) { 370 m.dkim = dkim 371 m.dkimSet = true 372 } 373 374 // EnableNativeSend allows the return path to match the address in the Message.Headers.From: 375 // field when sending from Mailgun rather than the usual bounce+ address in the return path. 376 func (m *Message) EnableNativeSend() { 377 m.nativeSend = true 378 } 379 380 // EnableTestMode allows submittal of a message, such that it will be discarded by Mailgun. 381 // This facilitates testing client-side software without actually consuming e-mail resources. 382 func (m *Message) EnableTestMode() { 383 m.testMode = true 384 } 385 386 // SetDeliveryTime schedules the message for transmission at the indicated time. 387 // Pass nil to remove any installed schedule. 388 // Refer to the Mailgun documentation for more information. 389 func (m *Message) SetDeliveryTime(dt time.Time) { 390 m.deliveryTime = dt 391 } 392 393 // SetTracking sets the o:tracking message parameter to adjust, on a message-by-message basis, 394 // whether or not Mailgun will rewrite URLs to facilitate event tracking. 395 // Events tracked includes opens, clicks, unsubscribes, etc. 396 // Note: simply calling this method ensures that the o:tracking header is passed in with the message. 397 // Its yes/no setting is determined by the call's parameter. 398 // Note that this header is not passed on to the final recipient(s). 399 // Refer to the Mailgun documentation for more information. 400 func (m *Message) SetTracking(tracking bool) { 401 m.tracking = tracking 402 m.trackingSet = true 403 } 404 405 // SetTrackingClicks information is found in the Mailgun documentation. 406 func (m *Message) SetTrackingClicks(trackingClicks bool) { 407 m.trackingClicks = trackingClicks 408 m.trackingClicksSet = true 409 } 410 411 // SetRequireTLS information is found in the Mailgun documentation. 412 func (m *Message) SetRequireTLS(b bool) { 413 m.requireTLS = b 414 } 415 416 // SetSkipVerification information is found in the Mailgun documentation. 417 func (m *Message) SetSkipVerification(b bool) { 418 m.skipVerification = b 419 } 420 421 //SetTrackingOpens information is found in the Mailgun documentation. 422 func (m *Message) SetTrackingOpens(trackingOpens bool) { 423 m.trackingOpens = trackingOpens 424 m.trackingOpensSet = true 425 } 426 427 // AddHeader allows you to send custom MIME headers with the message. 428 func (m *Message) AddHeader(header, value string) { 429 if m.headers == nil { 430 m.headers = make(map[string]string) 431 } 432 m.headers[header] = value 433 } 434 435 // AddVariable lets you associate a set of variables with messages you send, 436 // which Mailgun can use to, in essence, complete form-mail. 437 // Refer to the Mailgun documentation for more information. 438 func (m *Message) AddVariable(variable string, value interface{}) error { 439 if m.variables == nil { 440 m.variables = make(map[string]string) 441 } 442 443 j, err := json.Marshal(value) 444 if err != nil { 445 return err 446 } 447 448 encoded := string(j) 449 v, err := strconv.Unquote(encoded) 450 if err != nil { 451 v = encoded 452 } 453 454 m.variables[variable] = v 455 return nil 456 } 457 458 // AddTemplateVariable adds a template variable to the map of template variables, replacing the variable if it is already there. 459 // This is used for server-side message templates and can nest arbitrary values. At send time, the resulting map will be converted into 460 // a JSON string and sent as a header in the X-Mailgun-Variables header. 461 func (m *Message) AddTemplateVariable(variable string, value interface{}) error { 462 if m.templateVariables == nil { 463 m.templateVariables = make(map[string]interface{}) 464 } 465 m.templateVariables[variable] = value 466 return nil 467 } 468 469 // AddDomain allows you to use a separate domain for the type of messages you are sending. 470 func (m *Message) AddDomain(domain string) { 471 m.domain = domain 472 } 473 474 // GetHeaders retrieves the http headers associated with this message 475 func (m *Message) GetHeaders() map[string]string { 476 return m.headers 477 } 478 479 // ErrInvalidMessage is returned by `Send()` when the `mailgun.Message` struct is incomplete 480 var ErrInvalidMessage = errors.New("message not valid") 481 482 // Send attempts to queue a message (see Message, NewMessage, and its methods) for delivery. 483 // It returns the Mailgun server response, which consists of two components: 484 // a human-readable status message, and a message ID. The status and message ID are set only 485 // if no error occurred. 486 func (mg *MailgunImpl) Send(ctx context.Context, message *Message) (mes string, id string, err error) { 487 if mg.domain == "" { 488 err = errors.New("you must provide a valid domain before calling Send()") 489 return 490 } 491 492 if mg.apiKey == "" { 493 err = errors.New("you must provide a valid api-key before calling Send()") 494 return 495 } 496 497 if !isValid(message) { 498 err = ErrInvalidMessage 499 return 500 } 501 payload := newFormDataPayload() 502 503 message.specific.addValues(payload) 504 for _, to := range message.to { 505 payload.addValue("to", to) 506 } 507 for _, tag := range message.tags { 508 payload.addValue("o:tag", tag) 509 } 510 for _, campaign := range message.campaigns { 511 payload.addValue("o:campaign", campaign) 512 } 513 if message.dkimSet { 514 payload.addValue("o:dkim", yesNo(message.dkim)) 515 } 516 if !message.deliveryTime.IsZero() { 517 payload.addValue("o:deliverytime", formatMailgunTime(message.deliveryTime)) 518 } 519 if message.nativeSend { 520 payload.addValue("o:native-send", "yes") 521 } 522 if message.testMode { 523 payload.addValue("o:testmode", "yes") 524 } 525 if message.trackingSet { 526 payload.addValue("o:tracking", yesNo(message.tracking)) 527 } 528 if message.trackingClicksSet { 529 payload.addValue("o:tracking-clicks", yesNo(message.trackingClicks)) 530 } 531 if message.trackingOpensSet { 532 payload.addValue("o:tracking-opens", yesNo(message.trackingOpens)) 533 } 534 if message.requireTLS { 535 payload.addValue("o:require-tls", trueFalse(message.requireTLS)) 536 } 537 if message.skipVerification { 538 payload.addValue("o:skip-verification", trueFalse(message.skipVerification)) 539 } 540 if message.headers != nil { 541 for header, value := range message.headers { 542 payload.addValue("h:"+header, value) 543 } 544 } 545 if message.variables != nil { 546 for variable, value := range message.variables { 547 payload.addValue("v:"+variable, value) 548 } 549 } 550 if message.templateVariables != nil { 551 variableString, err := json.Marshal(message.templateVariables) 552 if err == nil { 553 // the map was marshalled as json so add it 554 payload.addValue("h:X-Mailgun-Variables", string(variableString)) 555 } 556 } 557 if message.recipientVariables != nil { 558 j, err := json.Marshal(message.recipientVariables) 559 if err != nil { 560 return "", "", err 561 } 562 payload.addValue("recipient-variables", string(j)) 563 } 564 if message.attachments != nil { 565 for _, attachment := range message.attachments { 566 payload.addFile("attachment", attachment) 567 } 568 } 569 if message.readerAttachments != nil { 570 for _, readerAttachment := range message.readerAttachments { 571 payload.addReadCloser("attachment", readerAttachment.Filename, readerAttachment.ReadCloser) 572 } 573 } 574 if message.bufferAttachments != nil { 575 for _, bufferAttachment := range message.bufferAttachments { 576 payload.addBuffer("attachment", bufferAttachment.Filename, bufferAttachment.Buffer) 577 } 578 } 579 if message.inlines != nil { 580 for _, inline := range message.inlines { 581 payload.addFile("inline", inline) 582 } 583 } 584 585 if message.readerInlines != nil { 586 for _, readerAttachment := range message.readerInlines { 587 payload.addReadCloser("inline", readerAttachment.Filename, readerAttachment.ReadCloser) 588 } 589 } 590 591 if message.domain == "" { 592 message.domain = mg.Domain() 593 } 594 595 r := newHTTPRequest(generateApiUrlWithDomain(mg, message.specific.endpoint(), message.domain)) 596 r.setClient(mg.Client()) 597 r.setBasicAuth(basicAuthUser, mg.APIKey()) 598 599 var response sendMessageResponse 600 err = postResponseFromJSON(ctx, r, payload, &response) 601 if err == nil { 602 mes = response.Message 603 id = response.Id 604 } 605 606 return 607 } 608 609 func (pm *plainMessage) addValues(p *formDataPayload) { 610 p.addValue("from", pm.from) 611 p.addValue("subject", pm.subject) 612 p.addValue("text", pm.text) 613 for _, cc := range pm.cc { 614 p.addValue("cc", cc) 615 } 616 for _, bcc := range pm.bcc { 617 p.addValue("bcc", bcc) 618 } 619 if pm.html != "" { 620 p.addValue("html", pm.html) 621 } 622 if pm.template != "" { 623 p.addValue("template", pm.template) 624 } 625 } 626 627 func (mm *mimeMessage) addValues(p *formDataPayload) { 628 p.addReadCloser("message", "message.mime", mm.body) 629 } 630 631 func (pm *plainMessage) endpoint() string { 632 return messagesEndpoint 633 } 634 635 func (mm *mimeMessage) endpoint() string { 636 return mimeMessagesEndpoint 637 } 638 639 // yesNo translates a true/false boolean value into a yes/no setting suitable for the Mailgun API. 640 func yesNo(b bool) string { 641 if b { 642 return "yes" 643 } 644 return "no" 645 } 646 647 func trueFalse(b bool) string { 648 if b { 649 return "true" 650 } 651 return "false" 652 } 653 654 // isValid returns true if, and only if, 655 // a Message instance is sufficiently initialized to send via the Mailgun interface. 656 func isValid(m *Message) bool { 657 if m == nil { 658 return false 659 } 660 661 if !m.specific.isValid() { 662 return false 663 } 664 665 if m.RecipientCount() == 0 { 666 return false 667 } 668 669 if !validateStringList(m.tags, false) { 670 return false 671 } 672 673 if !validateStringList(m.campaigns, false) || len(m.campaigns) > 3 { 674 return false 675 } 676 677 return true 678 } 679 680 func (pm *plainMessage) isValid() bool { 681 if pm.from == "" { 682 return false 683 } 684 685 if !validateStringList(pm.cc, false) { 686 return false 687 } 688 689 if !validateStringList(pm.bcc, false) { 690 return false 691 } 692 693 if pm.template != "" { 694 // pm.text or pm.html not needed if template is supplied 695 return true 696 } 697 698 if pm.text == "" && pm.html == "" { 699 return false 700 } 701 702 return true 703 } 704 705 func (mm *mimeMessage) isValid() bool { 706 return mm.body != nil 707 } 708 709 // validateStringList returns true if, and only if, 710 // a slice of strings exists AND all of its elements exist, 711 // OR if the slice doesn't exist AND it's not required to exist. 712 // The requireOne parameter indicates whether the list is required to exist. 713 func validateStringList(list []string, requireOne bool) bool { 714 hasOne := false 715 716 if list == nil { 717 return !requireOne 718 } else { 719 for _, a := range list { 720 if a == "" { 721 return false 722 } else { 723 hasOne = hasOne || true 724 } 725 } 726 } 727 728 return hasOne 729 } 730 731 // GetStoredMessage retrieves information about a received e-mail message. 732 // This provides visibility into, e.g., replies to a message sent to a mailing list. 733 func (mg *MailgunImpl) GetStoredMessage(ctx context.Context, url string) (StoredMessage, error) { 734 r := newHTTPRequest(url) 735 r.setClient(mg.Client()) 736 r.setBasicAuth(basicAuthUser, mg.APIKey()) 737 738 var response StoredMessage 739 err := getResponseFromJSON(ctx, r, &response) 740 return response, err 741 } 742 743 // Given a storage id resend the stored message to the specified recipients 744 func (mg *MailgunImpl) ReSend(ctx context.Context, url string, recipients ...string) (string, string, error) { 745 r := newHTTPRequest(url) 746 r.setClient(mg.Client()) 747 r.setBasicAuth(basicAuthUser, mg.APIKey()) 748 749 payload := newFormDataPayload() 750 751 if len(recipients) == 0 { 752 return "", "", errors.New("must provide at least one recipient") 753 } 754 755 for _, to := range recipients { 756 payload.addValue("to", to) 757 } 758 759 var resp sendMessageResponse 760 err := postResponseFromJSON(ctx, r, payload, &resp) 761 if err != nil { 762 return "", "", err 763 } 764 return resp.Message, resp.Id, nil 765 766 } 767 768 // GetStoredMessageRaw retrieves the raw MIME body of a received e-mail message. 769 // Compared to GetStoredMessage, it gives access to the unparsed MIME body, and 770 // thus delegates to the caller the required parsing. 771 func (mg *MailgunImpl) GetStoredMessageRaw(ctx context.Context, url string) (StoredMessageRaw, error) { 772 r := newHTTPRequest(url) 773 r.setClient(mg.Client()) 774 r.setBasicAuth(basicAuthUser, mg.APIKey()) 775 r.addHeader("Accept", "message/rfc2822") 776 777 var response StoredMessageRaw 778 err := getResponseFromJSON(ctx, r, &response) 779 return response, err 780 } 781 782 // Deprecated: Use GetStoreMessage() instead 783 func (mg *MailgunImpl) GetStoredMessageForURL(ctx context.Context, url string) (StoredMessage, error) { 784 return mg.GetStoredMessage(ctx, url) 785 } 786 787 // Deprecated: Use GetStoreMessageRaw() instead 788 func (mg *MailgunImpl) GetStoredMessageRawForURL(ctx context.Context, url string) (StoredMessageRaw, error) { 789 return mg.GetStoredMessageRaw(ctx, url) 790 } 791 792 // GetStoredAttachment retrieves the raw MIME body of a received e-mail message attachment. 793 func (mg *MailgunImpl) GetStoredAttachment(ctx context.Context, url string) ([]byte, error) { 794 r := newHTTPRequest(url) 795 r.setClient(mg.Client()) 796 r.setBasicAuth(basicAuthUser, mg.APIKey()) 797 r.addHeader("Accept", "message/rfc2822") 798 799 response, err := makeGetRequest(ctx, r) 800 801 return response.Data, err 802 }