package mailgun import ( "strconv" "time" ) // Bounce aggregates data relating to undeliverable messages to a specific intended recipient, // identified by Address. // Code provides the SMTP error code causing the bounce, // while Error provides a human readable reason why. // CreatedAt provides the time at which Mailgun detected the bounce. type Bounce struct { CreatedAt string `json:"created_at"` code interface{} `json:"code"` Address string `json:"address"` Error string `json:"error"` } type Paging struct { First string `json:"first"` Next string `json:"next"` Previous string `json:"previous"` Last string `json:"last"` } type bounceEnvelope struct { Items []Bounce `json:"items"` Paging Paging `json:"paging"` } // GetCreatedAt parses the textual, RFC-822 timestamp into a standard Go-compatible // Time structure. func (i Bounce) GetCreatedAt() (t time.Time, err error) { return parseMailgunTime(i.CreatedAt) } // GetCode will return the bounce code for the message, regardless if it was // returned as a string or as an integer. This method overcomes a protocol // bug in the Mailgun API. func (b Bounce) GetCode() (int, error) { switch c := b.code.(type) { case int: return c, nil case string: return strconv.Atoi(c) default: return -1, strconv.ErrSyntax } } // GetBounces returns a complete set of bounces logged against the sender's domain, if any. // The results include the total number of bounces (regardless of skip or limit settings), // and the slice of bounces specified, if successful. // Note that the length of the slice may be smaller than the total number of bounces. func (m *MailgunImpl) GetBounces(limit, skip int) (int, []Bounce, error) { r := newHTTPRequest(generateApiUrl(m, bouncesEndpoint)) if limit != -1 { r.addParameter("limit", strconv.Itoa(limit)) } if skip != -1 { r.addParameter("skip", strconv.Itoa(skip)) } r.setClient(m.Client()) r.setBasicAuth(basicAuthUser, m.ApiKey()) var response bounceEnvelope err := getResponseFromJSON(r, &response) if err != nil { return -1, nil, err } return len(response.Items), response.Items, nil } // GetSingleBounce retrieves a single bounce record, if any exist, for the given recipient address. func (m *MailgunImpl) GetSingleBounce(address string) (Bounce, error) { r := newHTTPRequest(generateApiUrl(m, bouncesEndpoint) + "/" + address) r.setClient(m.Client()) r.setBasicAuth(basicAuthUser, m.ApiKey()) var response Bounce err := getResponseFromJSON(r, &response) return response, err } // AddBounce files a bounce report. // Address identifies the intended recipient of the message that bounced. // Code corresponds to the numeric response given by the e-mail server which rejected the message. // Error providees the corresponding human readable reason for the problem. // For example, // here's how the these two fields relate. // Suppose the SMTP server responds with an error, as below. // Then, . . . // // 550 Requested action not taken: mailbox unavailable // \___/\_______________________________________________/ // | | // `-- Code `-- Error // // Note that both code and error exist as strings, even though // code will report as a number. func (m *MailgunImpl) AddBounce(address, code, error string) error { r := newHTTPRequest(generateApiUrl(m, bouncesEndpoint)) r.setClient(m.Client()) r.setBasicAuth(basicAuthUser, m.ApiKey()) payload := newUrlEncodedPayload() payload.addValue("address", address) if code != "" { payload.addValue("code", code) } if error != "" { payload.addValue("error", error) } _, err := makePostRequest(r, payload) return err } // DeleteBounce removes all bounces associted with the provided e-mail address. func (m *MailgunImpl) DeleteBounce(address string) error { r := newHTTPRequest(generateApiUrl(m, bouncesEndpoint) + "/" + address) r.setClient(m.Client()) r.setBasicAuth(basicAuthUser, m.ApiKey()) _, err := makeDeleteRequest(r) return err }