package mailgun import ( "context" "strconv" ) // Bounce aggregates data relating to undeliverable messages to a specific intended recipient, // identified by Address. type Bounce struct { // The time at which Mailgun detected the bounce. CreatedAt RFC2822Time `json:"created_at"` // Code provides the SMTP error code that caused the bounce Code string `json:"code"` // Address the bounce is for Address string `json:"address"` // human readable reason why Error string `json:"error"` } type Paging struct { First string `json:"first,omitempty"` Next string `json:"next,omitempty"` Previous string `json:"previous,omitempty"` Last string `json:"last,omitempty"` } type bouncesListResponse struct { Items []Bounce `json:"items"` Paging Paging `json:"paging"` } // ListBounces 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 (mg *MailgunImpl) ListBounces(opts *ListOptions) *BouncesIterator { r := newHTTPRequest(generateApiUrl(mg, bouncesEndpoint)) r.setClient(mg.Client()) r.setBasicAuth(basicAuthUser, mg.APIKey()) if opts != nil { if opts.Limit != 0 { r.addParameter("limit", strconv.Itoa(opts.Limit)) } } url, err := r.generateUrlWithParameters() return &BouncesIterator{ mg: mg, bouncesListResponse: bouncesListResponse{Paging: Paging{Next: url, First: url}}, err: err, } } type BouncesIterator struct { bouncesListResponse mg Mailgun err error } // If an error occurred during iteration `Err()` will return non nil func (ci *BouncesIterator) Err() error { return ci.err } // Next retrieves the next page of items from the api. Returns false when there // no more pages to retrieve or if there was an error. Use `.Err()` to retrieve // the error func (ci *BouncesIterator) Next(ctx context.Context, items *[]Bounce) bool { if ci.err != nil { return false } ci.err = ci.fetch(ctx, ci.Paging.Next) if ci.err != nil { return false } cpy := make([]Bounce, len(ci.Items)) copy(cpy, ci.Items) *items = cpy if len(ci.Items) == 0 { return false } return true } // First retrieves the first page of items from the api. Returns false if there // was an error. It also sets the iterator object to the first page. // Use `.Err()` to retrieve the error. func (ci *BouncesIterator) First(ctx context.Context, items *[]Bounce) bool { if ci.err != nil { return false } ci.err = ci.fetch(ctx, ci.Paging.First) if ci.err != nil { return false } cpy := make([]Bounce, len(ci.Items)) copy(cpy, ci.Items) *items = cpy return true } // Last retrieves the last page of items from the api. // Calling Last() is invalid unless you first call First() or Next() // Returns false if there was an error. It also sets the iterator object // to the last page. Use `.Err()` to retrieve the error. func (ci *BouncesIterator) Last(ctx context.Context, items *[]Bounce) bool { if ci.err != nil { return false } ci.err = ci.fetch(ctx, ci.Paging.Last) if ci.err != nil { return false } cpy := make([]Bounce, len(ci.Items)) copy(cpy, ci.Items) *items = cpy return true } // Previous retrieves the previous page of items from the api. Returns false when there // no more pages to retrieve or if there was an error. Use `.Err()` to retrieve // the error if any func (ci *BouncesIterator) Previous(ctx context.Context, items *[]Bounce) bool { if ci.err != nil { return false } if ci.Paging.Previous == "" { return false } ci.err = ci.fetch(ctx, ci.Paging.Previous) if ci.err != nil { return false } cpy := make([]Bounce, len(ci.Items)) copy(cpy, ci.Items) *items = cpy if len(ci.Items) == 0 { return false } return true } func (ci *BouncesIterator) fetch(ctx context.Context, url string) error { r := newHTTPRequest(url) r.setClient(ci.mg.Client()) r.setBasicAuth(basicAuthUser, ci.mg.APIKey()) return getResponseFromJSON(ctx, r, &ci.bouncesListResponse) } // GetBounce retrieves a single bounce record, if any exist, for the given recipient address. func (mg *MailgunImpl) GetBounce(ctx context.Context, address string) (Bounce, error) { r := newHTTPRequest(generateApiUrl(mg, bouncesEndpoint) + "/" + address) r.setClient(mg.Client()) r.setBasicAuth(basicAuthUser, mg.APIKey()) var response Bounce err := getResponseFromJSON(ctx, 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 (mg *MailgunImpl) AddBounce(ctx context.Context, address, code, error string) error { r := newHTTPRequest(generateApiUrl(mg, bouncesEndpoint)) r.setClient(mg.Client()) r.setBasicAuth(basicAuthUser, mg.APIKey()) payload := newUrlEncodedPayload() payload.addValue("address", address) if code != "" { payload.addValue("code", code) } if error != "" { payload.addValue("error", error) } _, err := makePostRequest(ctx, r, payload) return err } // DeleteBounce removes all bounces associted with the provided e-mail address. func (mg *MailgunImpl) DeleteBounce(ctx context.Context, address string) error { r := newHTTPRequest(generateApiUrl(mg, bouncesEndpoint) + "/" + address) r.setClient(mg.Client()) r.setBasicAuth(basicAuthUser, mg.APIKey()) _, err := makeDeleteRequest(ctx, r) return err }