mailgun dep

This commit is contained in:
Patrick Nagurny
2020-11-23 11:12:53 -05:00
parent b011b28cf5
commit ece803ec68
242 changed files with 25496 additions and 11732 deletions

3
vendor/github.com/mailgun/mailgun-go/v4/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
.DS_Store
.idea/
cmd/mailgun/mailgun

7
vendor/github.com/mailgun/mailgun-go/v4/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,7 @@
language: go
env:
- GO111MODULE=on
go:
- 1.11.x

216
vendor/github.com/mailgun/mailgun-go/v4/CHANGELOG generated vendored Normal file
View File

@@ -0,0 +1,216 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.3.0] - 2020-10-14
### Changed
* Replaced easyjson with json-iterator when marshalling events
* Modified the mailgun.Event interface by removing the Marshaller interface from easyjson.
* Fixed failure while testing webhook via mailgun web console
## [4.2.0] - 2020-09-17
### Added
* Added ListEventsWithDomain()
## [4.1.4] - 2020-08-20
### Changes
* Added Storage to Accepted, Delivered and Failed events
## [4.1.3] - 2020-06-23
### Changes
* UpdateTemplateVersion() now including the template html in the payload request
## [4.1.2] - 2020-06-10
### Added
* Added DeleteBounceList method
## [4.1.1] - 2020-06-05
### Changed
* Nows sets initial tag when creating a new template
## [4.1.0] - 2020-04-23
### Changed
* Added EmailVerification.reason is now a []string (Fixes #217)
## [4.0.1] - 2020-03-10
### Added
* Added SetTemplateVersion and SetTemplateRenderText methods to Message
## [4.0.0] - 2020-01-27
### Changes
* Changed `UserVariables` type from `map[string]interface{}` to `interface{}`
to handle truncated user-variable messages in events.
### Added
* Add support for setting AMP content in messages
## [3.6.3] - 2019-12-03
### Changes
* Calls to get stats now use epoch as the time format
## [3.6.2] - 2019-11-18
### Added
* Added `AddTemplateVariable()` to make adding variables to templates
less confusing and error prone.
## [3.6.1] - 2019-10-24
### Added
* Added `VerifyWebhookSignature()` to mailgun interface
## [3.6.1-rc.3] - 2019-07-16
### Added
* APIBaseEU and APIBaseUS to help customers change regions
* Documented how to change regions in the README
## [3.6.1-rc.2] - 2019-07-01
### Changes
* Fix the JSON response for `GetMember()`
* Typo in format string in max number of tags error
## [3.6.0] - 2019-06-26
### Added
* Added UpdateClickTracking() to modify click tracking for a domain
* Added UpdateUnsubscribeTracking() to modify unsubscribe tracking for a domain
* Added UpdateOpenTracking() to modify open tracking for a domain
## [3.5.0] - 2019-05-21
### Added
* Added notice in README about go dep bug.
* Added endpoints for webhooks in mock server
### Changes
* Change names of some parameters on public methods to make their use clearer.
* Changed signature of `GetWebhook()` now returns []string.
* Changed signature of `ListWebhooks()` now returns map[string][]string.
* Both `GetWebhooks()` and `ListWebhooks()` now handle new and legacy webhooks properly.
## [3.4.0] - 2019-04-23
### Added
* Added `Message.SetTemplate()` to allow sending with the body of a template.
### Changes
* Changed signature of `CreateDomain()` moved password into `CreateDomainOptions`
## [3.4.0] - 2019-04-23
### Added
* Added `Message.SetTemplate()` to allow sending with the body of a template.
### Changes
* Changed signature of `CreateDomain()` moved password into `CreateDomainOptions`
## [3.3.2] - 2019-03-28
### Changes
* Uncommented DeliveryStatus.Code and change it to an integer (See #175)
* Added UserVariables to all Message events (See #176)
## [3.3.1] - 2019-03-13
### Changes
* Updated Template calls to reflect the most recent Template API changes.
* GetStoredMessage() now accepts a URL instead of an id
* Deprecated GetStoredMessageForURL()
* Deprecated GetStoredMessageRawForURL()
* Fixed GetUnsubscribed()
### Added
* Added `GetStoredAttachment()`
### Removed
* Method `DeleteStoredMessage()` mailgun API no long allows this call
## [3.3.0] - 2019-01-28
### Changes
* Changed signature of CreateDomain() Now returns JSON response
* Changed signature of GetDomain() Now returns a single DomainResponse
* Clarified installation notes for non golang module users
* Changed 'Public Key' to 'Public Validation Key' in readme
* Fixed issue with Next() for limit/skip based iterators
### Added
* Added VerifyDomain()
## [3.2.0] - 2019-01-21
### Changes
* Deprecated mg.VerifyWebhookRequest()
### Added
* Added mailgun.ParseEvent()
* Added mailgun.ParseEvents()
* Added mg.VerifyWebhookSignature()
## [3.1.0] - 2019-01-16
### Changes
* Removed context.Context from ListDomains() signature
* ListEventOptions.Begin and End are no longer pointers to time.Time
### Added
* Added mg.ReSend() to public Mailgun interface
* Added Message.SetSkipVerification()
* Added Message.SetRequireTLS()
## [3.0.0] - 2019-01-15
### Added
* Added CHANGELOG
* Added `AddDomainIP()`
* Added `ListDomainIPS()`
* Added `DeleteDomainIP()`
* Added `ListIPS()`
* Added `GetIP()`
* Added `GetDomainTracking()`
* Added `GetDomainConnection()`
* Added `UpdateDomainConnection()`
* Added `CreateExport()`
* Added `ListExports()`
* Added `GetExports()`
* Added `GetExportLink()`
* Added `CreateTemplate()`
* Added `GetTemplate()`
* Added `UpdateTemplate()`
* Added `DeleteTemplate()`
* Added `ListTemplates()`
* Added `AddTemplateVersion()`
* Added `GetTemplateVersion()`
* Added `UpdateTemplateVersion()`
* Added `DeleteTemplateVersion()`
* Added `ListTemplateVersions()`
### Changed
* Added a `mailgun.MockServer` which duplicates part of the mailgun API; suitable for testing
* `ListMailingLists()` now uses the `/pages` API and returns an iterator
* `ListMembers()` now uses the `/pages` API and returns an iterator
* Renamed public interface methods to be consistent. IE: `GetThing(), ListThing(), CreateThing()`
* Moved event objects into the `mailgun/events` package, so names like
`MailingList` returned by API calls and `MailingList` as an event object
don't conflict and confuse users.
* Now using context.Context for all network operations
* Test suite will run without MG_ env vars defined
* ListRoutes() now uses the iterator interface
* Added SkipNetworkTest()
* Renamed GetStatsTotals() to GetStats()
* Renamed GetUnsubscribes to ListUnsubscribes()
* Renamed Unsubscribe() to CreateUnsubscribe()
* Renamed RemoveUnsubscribe() to DeleteUnsubscribe()
* GetStats() now takes an `*opt` argument to pass optional parameters
* Modified GetUnsubscribe() to follow the API
* Now using golang modules
* ListCredentials() now returns an iterator
* ListUnsubscribes() now returns an paging iterator
* CreateDomain now accepts CreateDomainOption{}
* CreateDomain() now supports all optional parameters not just spam_action and wildcard.
* ListComplaints() now returns a page iterator
* Renamed `TagItem` to `Tag`
* ListBounces() now returns a page iterator
* API responses with CreatedAt fields are now unmarshalled into RFC2822
* DomainList() now returns an iterator
* Updated godoc documentation
* Renamed ApiBase to APIBase
* Updated copyright to 2019
* `ListEvents()` now returns a list of typed events
### Removed
* Removed more deprecated types
* Removed gobuffalo/envy dependency
* Remove mention of the CLI in the README
* Removed mailgun cli from project
* Removed GetCode() from `Bounce` struct. Verified API returns 'string' and not 'int'
* Removed deprecated methods NewMessage and NewMIMEMessage
* Removed ginkgo and gomega tests
* Removed GetStats() As the /stats endpoint is depreciated

27
vendor/github.com/mailgun/mailgun-go/v4/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2013-2016, Michael Banzon
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the names of Mailgun, Michael Banzon, nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

15
vendor/github.com/mailgun/mailgun-go/v4/Makefile generated vendored Normal file
View File

@@ -0,0 +1,15 @@
.PHONY: all
.DEFAULT_GOAL := all
PACKAGE := github.com/mailgun/mailgun-go
all:
export GO111MODULE=on; go test . -v
godoc:
mkdir -p /tmp/tmpgoroot/doc
-rm -rf /tmp/tmpgopath/src/${PACKAGE}
mkdir -p /tmp/tmpgopath/src/${PACKAGE}
tar -c --exclude='.git' --exclude='tmp' . | tar -x -C /tmp/tmpgopath/src/${PACKAGE}
echo -e "open http://localhost:6060/pkg/${PACKAGE}\n"
GOROOT=/tmp/tmpgoroot/ GOPATH=/tmp/tmpgopath/ godoc -http=localhost:6060

363
vendor/github.com/mailgun/mailgun-go/v4/README.md generated vendored Normal file
View File

@@ -0,0 +1,363 @@
# Mailgun with Go
[![GoDoc](https://godoc.org/github.com/mailgun/mailgun-go?status.svg)](https://godoc.org/github.com/mailgun/mailgun-go)
[![Build Status](https://img.shields.io/travis/mailgun/mailgun-go/master.svg)](https://travis-ci.org/mailgun/mailgun-go)
Go library for interacting with the [Mailgun](https://mailgun.com/) [API](https://documentation.mailgun.com/api_reference.html).
## Usage
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/mailgun/mailgun-go/v4"
)
// Your available domain names can be found here:
// (https://app.mailgun.com/app/domains)
var yourDomain string = "your-domain-name" // e.g. mg.yourcompany.com
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
var privateAPIKey string = "your-private-key"
func main() {
// Create an instance of the Mailgun Client
mg := mailgun.NewMailgun(yourDomain, privateAPIKey)
sender := "sender@example.com"
subject := "Fancy subject!"
body := "Hello from Mailgun Go!"
recipient := "recipient@example.com"
// The message object allows you to add attachments and Bcc recipients
message := mg.NewMessage(sender, subject, body, recipient)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Send the message with a 10 second timeout
resp, id, err := mg.Send(ctx, message)
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %s Resp: %s\n", id, resp)
}
```
## Get Events
```go
package main
import (
"context"
"fmt"
"time"
"github.com/mailgun/mailgun-go/v4"
"github.com/mailgun/mailgun-go/v4/events"
)
func main() {
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
mg := mailgun.NewMailgun("your-domain.com", "your-private-key")
it := mg.ListEvents(&mailgun.ListEventOptions{Limit: 100})
var page []mailgun.Event
// The entire operation should not take longer than 30 seconds
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
// For each page of 100 events
for it.Next(ctx, &page) {
for _, e := range page {
// You can access some fields via the interface
fmt.Printf("Event: '%s' TimeStamp: '%s'\n", e.GetName(), e.GetTimestamp())
// and you can act upon each event by type
switch event := e.(type) {
case *events.Accepted:
fmt.Printf("Accepted: auth: %t\n", event.Flags.IsAuthenticated)
case *events.Delivered:
fmt.Printf("Delivered transport: %s\n", event.Envelope.Transport)
case *events.Failed:
fmt.Printf("Failed reason: %s\n", event.Reason)
case *events.Clicked:
fmt.Printf("Clicked GeoLocation: %s\n", event.GeoLocation.Country)
case *events.Opened:
fmt.Printf("Opened GeoLocation: %s\n", event.GeoLocation.Country)
case *events.Rejected:
fmt.Printf("Rejected reason: %s\n", event.Reject.Reason)
case *events.Stored:
fmt.Printf("Stored URL: %s\n", event.Storage.URL)
case *events.Unsubscribed:
fmt.Printf("Unsubscribed client OS: %s\n", event.ClientInfo.ClientOS)
}
}
}
}
```
## Event Polling
The mailgun library has built-in support for polling the events api
```go
package main
import (
"context"
"time"
"github.com/mailgun/mailgun-go/v4"
)
func main() {
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
mg := mailgun.NewMailgun("your-domain.com", "your-private-key")
begin := time.Now().Add(time.Second * -3)
// Very short poll interval
it := mg.PollEvents(&mailgun.ListEventOptions{
// Only events with a timestamp after this date/time will be returned
Begin: &begin,
// How often we poll the api for new events
PollInterval: time.Second * 30,
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Poll until our email event arrives
var page []mailgun.Event
for it.Poll(ctx, &page) {
for _, e := range page {
// Do something with event
}
}
}
```
# Email Validations
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/mailgun/mailgun-go/v4"
)
// If your plan does not include email validations but you have an account,
// use your Public Validation api key. If your plan does include email validations,
// use your Private API key. You can find both the Private and
// Public Validation API Keys in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
var apiKey string = "your-api-key"
func main() {
// To use the /v4 version of validations define MG_URL in the environment
// as `https://api.mailgun.net/v4` or set `v.SetAPIBase("https://api.mailgun.net/v4")`
// Create an instance of the Validator
v := mailgun.NewEmailValidator(apiKey)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
email, err := v.ValidateEmail(ctx, "recipient@example.com", false)
if err != nil {
panic(err)
}
fmt.Printf("Valid: %t\n", email.IsValid)
}
```
## Webhook Handling
```go
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"github.com/mailgun/mailgun-go/v4"
"github.com/mailgun/mailgun-go/v4/events"
)
func main() {
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
mg := mailgun.NewMailgun("your-domain.com", "private-api-key")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var payload mailgun.WebhookPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
fmt.Printf("decode JSON error: %s", err)
w.WriteHeader(http.StatusNotAcceptable)
return
}
verified, err := mg.VerifyWebhookSignature(payload.Signature)
if err != nil {
fmt.Printf("verify error: %s\n", err)
w.WriteHeader(http.StatusNotAcceptable)
return
}
if !verified {
w.WriteHeader(http.StatusNotAcceptable)
fmt.Printf("failed verification %+v\n", payload.Signature)
return
}
fmt.Printf("Verified Signature\n")
// Parse the event provided by the webhook payload
e, err := mailgun.ParseEvent(payload.EventData)
if err != nil {
fmt.Printf("parse event error: %s\n", err)
return
}
switch event := e.(type) {
case *events.Accepted:
fmt.Printf("Accepted: auth: %t\n", event.Flags.IsAuthenticated)
case *events.Delivered:
fmt.Printf("Delivered transport: %s\n", event.Envelope.Transport)
}
})
fmt.Println("Serve on :9090...")
if err := http.ListenAndServe(":9090", nil); err != nil {
fmt.Printf("serve error: %s\n", err)
os.Exit(1)
}
}
```
## Using Templates
Templates enable you to create message templates on your Mailgun account and then populate the data variables at send-time. This allows you to have your layout and design managed on the server and handle the data on the client. The template variables are added as a JSON stringified `X-Mailgun-Variables` header. For example, if you have a template to send a password reset link, you could do the following:
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/mailgun/mailgun-go/v4"
)
// Your available domain names can be found here:
// (https://app.mailgun.com/app/domains)
var yourDomain string = "your-domain-name" // e.g. mg.yourcompany.com
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
var privateAPIKey string = "your-private-key"
func main() {
// Create an instance of the Mailgun Client
mg := mailgun.NewMailgun(yourDomain, privateAPIKey)
sender := "sender@example.com"
subject := "Fancy subject!"
body := ""
recipient := "recipient@example.com"
// The message object allows you to add attachments and Bcc recipients
message := mg.NewMessage(sender, subject, body, recipient)
message.SetTemplate("passwordReset")
message.AddTemplateVariable("passwordResetLink", "some link to your site unique to your user")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Send the message with a 10 second timeout
resp, id, err := mg.Send(ctx, message)
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %s Resp: %s\n", id, resp)
}
```
The official mailgun documentation includes examples using this library. Go
[here](https://documentation.mailgun.com/en/latest/api_reference.html#api-reference)
and click on the "Go" button at the top of the page.
### EU Region
European customers will need to change the default API Base to access your domains
```go
mg := mailgun.NewMailgun("your-domain.com", "private-api-key")
mg.SetAPIBase(mailgun.APIBaseEU)
```
## Installation
If you are using [golang modules](https://github.com/golang/go/wiki/Modules) make sure you
include the `/v4` at the end of your import paths
```bash
$ go get github.com/mailgun/mailgun-go/v4
```
If you are **not** using golang modules, you can drop the `/v4` at the end of the import path.
As long as you are using the latest 1.10 or 1.11 golang release, import paths that end in `/v4`
in your code should work fine even if you do not have golang modules enabled for your project.
```bash
$ go get github.com/mailgun/mailgun-go
```
**NOTE for go dep users**
Using version 3 of the mailgun-go library with go dep currently results in the following error
```
"github.com/mailgun/mailgun-go/v4/events", which contains malformed code: no package exists at ...
```
This is a known bug in go dep. You can follow the PR to fix this bug [here](https://github.com/golang/dep/pull/1963)
Until this bug is fixed, the best way to use version 3 of the mailgun-go library is to use the golang community
supported [golang modules](https://github.com/golang/go/wiki/Modules).
## Testing
*WARNING* - running the tests will cost you money!
To run the tests various environment variables must be set. These are:
* `MG_DOMAIN` is the domain name - this is a value registered in the Mailgun admin interface.
* `MG_PUBLIC_API_KEY` is the Public Validation API key - you can get this value from the Mailgun [security page](https://app.mailgun.com/app/account/security)
* `MG_API_KEY` is the Private API key - you can get this value from the Mailgun [security page](https://app.mailgun.com/app/account/security)
* `MG_EMAIL_TO` is the email address used in various sending tests.
and finally
* `MG_SPEND_MONEY` if this value is set the part of the test that use the API to actually send email will be run - be aware *this will count on your quota* and *this _will_ cost you money*.
The code is released under a 3-clause BSD license. See the LICENSE file for more information.

211
vendor/github.com/mailgun/mailgun-go/v4/bounces.go generated vendored Normal file
View File

@@ -0,0 +1,211 @@
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
}
// DeleteBounceList removes all bounces in the bounce list
func (mg *MailgunImpl) DeleteBounceList(ctx context.Context) error {
r := newHTTPRequest(generateApiUrl(mg, bouncesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}

216
vendor/github.com/mailgun/mailgun-go/v4/credentials.go generated vendored Normal file
View File

@@ -0,0 +1,216 @@
package mailgun
import (
"context"
"fmt"
"strconv"
)
// A Credential structure describes a principle allowed to send or receive mail at the domain.
type Credential struct {
CreatedAt RFC2822Time `json:"created_at"`
Login string `json:"login"`
Password string `json:"password"`
}
type credentialsListResponse struct {
// is -1 if Next() or First() have not been called
TotalCount int `json:"total_count"`
Items []Credential `json:"items"`
}
// Returned when a required parameter is missing.
var ErrEmptyParam = fmt.Errorf("empty or illegal parameter")
// ListCredentials returns the (possibly zero-length) list of credentials associated with your domain.
func (mg *MailgunImpl) ListCredentials(opts *ListOptions) *CredentialsIterator {
var limit int
if opts != nil {
limit = opts.Limit
}
if limit == 0 {
limit = 100
}
return &CredentialsIterator{
mg: mg,
url: generateCredentialsUrl(mg, ""),
credentialsListResponse: credentialsListResponse{TotalCount: -1},
limit: limit,
}
}
type CredentialsIterator struct {
credentialsListResponse
limit int
mg Mailgun
offset int
url string
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ri *CredentialsIterator) Err() error {
return ri.err
}
// Offset returns the current offset of the iterator
func (ri *CredentialsIterator) Offset() int {
return ri.offset
}
// 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 (ri *CredentialsIterator) Next(ctx context.Context, items *[]Credential) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Credential, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
ri.offset = ri.offset + len(ri.Items)
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 (ri *CredentialsIterator) First(ctx context.Context, items *[]Credential) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, 0, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Credential, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
ri.offset = len(ri.Items)
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 (ri *CredentialsIterator) Last(ctx context.Context, items *[]Credential) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.TotalCount - ri.limit
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Credential, len(ri.Items))
copy(cpy, ri.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 (ri *CredentialsIterator) Previous(ctx context.Context, items *[]Credential) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.offset - (ri.limit * 2)
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Credential, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
return true
}
func (ri *CredentialsIterator) fetch(ctx context.Context, skip, limit int) error {
r := newHTTPRequest(ri.url)
r.setBasicAuth(basicAuthUser, ri.mg.APIKey())
r.setClient(ri.mg.Client())
if skip != 0 {
r.addParameter("skip", strconv.Itoa(skip))
}
if limit != 0 {
r.addParameter("limit", strconv.Itoa(limit))
}
return getResponseFromJSON(ctx, r, &ri.credentialsListResponse)
}
// CreateCredential attempts to create associate a new principle with your domain.
func (mg *MailgunImpl) CreateCredential(ctx context.Context, login, password string) error {
if (login == "") || (password == "") {
return ErrEmptyParam
}
r := newHTTPRequest(generateCredentialsUrl(mg, ""))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("login", login)
p.addValue("password", password)
_, err := makePostRequest(ctx, r, p)
return err
}
// ChangeCredentialPassword attempts to alter the indicated credential's password.
func (mg *MailgunImpl) ChangeCredentialPassword(ctx context.Context, login, password string) error {
if (login == "") || (password == "") {
return ErrEmptyParam
}
r := newHTTPRequest(generateCredentialsUrl(mg, login))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("password", password)
_, err := makePutRequest(ctx, r, p)
return err
}
// DeleteCredential attempts to remove the indicated principle from the domain.
func (mg *MailgunImpl) DeleteCredential(ctx context.Context, login string) error {
if login == "" {
return ErrEmptyParam
}
r := newHTTPRequest(generateCredentialsUrl(mg, login))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}

386
vendor/github.com/mailgun/mailgun-go/v4/domains.go generated vendored Normal file
View File

@@ -0,0 +1,386 @@
package mailgun
import (
"context"
"strconv"
"strings"
)
// Use these to specify a spam action when creating a new domain.
const (
// Tag the received message with headers providing a measure of its spamness.
SpamActionTag = SpamAction("tag")
// Prevents Mailgun from taking any action on what it perceives to be spam.
SpamActionDisabled = SpamAction("disabled")
// instructs Mailgun to just block or delete the message all-together.
SpamActionDelete = SpamAction("delete")
)
type SpamAction string
// A Domain structure holds information about a domain used when sending mail.
type Domain struct {
CreatedAt RFC2822Time `json:"created_at"`
SMTPLogin string `json:"smtp_login"`
Name string `json:"name"`
SMTPPassword string `json:"smtp_password"`
Wildcard bool `json:"wildcard"`
SpamAction SpamAction `json:"spam_action"`
State string `json:"state"`
}
// DNSRecord structures describe intended records to properly configure your domain for use with Mailgun.
// Note that Mailgun does not host DNS records.
type DNSRecord struct {
Priority string
RecordType string `json:"record_type"`
Valid string
Name string
Value string
}
type DomainResponse struct {
Domain Domain `json:"domain"`
ReceivingDNSRecords []DNSRecord `json:"receiving_dns_records"`
SendingDNSRecords []DNSRecord `json:"sending_dns_records"`
}
type domainConnectionResponse struct {
Connection DomainConnection `json:"connection"`
}
type domainsListResponse struct {
// is -1 if Next() or First() have not been called
TotalCount int `json:"total_count"`
Items []Domain `json:"items"`
}
// Specify the domain connection options
type DomainConnection struct {
RequireTLS bool `json:"require_tls"`
SkipVerification bool `json:"skip_verification"`
}
// Specify the domain tracking options
type DomainTracking struct {
Click TrackingStatus `json:"click"`
Open TrackingStatus `json:"open"`
Unsubscribe TrackingStatus `json:"unsubscribe"`
}
// The tracking status of a domain
type TrackingStatus struct {
Active bool `json:"active"`
HTMLFooter string `json:"html_footer"`
TextFooter string `json:"text_footer"`
}
type domainTrackingResponse struct {
Tracking DomainTracking `json:"tracking"`
}
// ListDomains retrieves a set of domains from Mailgun.
func (mg *MailgunImpl) ListDomains(opts *ListOptions) *DomainsIterator {
var limit int
if opts != nil {
limit = opts.Limit
}
if limit == 0 {
limit = 100
}
return &DomainsIterator{
mg: mg,
url: generatePublicApiUrl(mg, domainsEndpoint),
domainsListResponse: domainsListResponse{TotalCount: -1},
limit: limit,
}
}
type DomainsIterator struct {
domainsListResponse
limit int
mg Mailgun
offset int
url string
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ri *DomainsIterator) Err() error {
return ri.err
}
// Offset returns the current offset of the iterator
func (ri *DomainsIterator) Offset() int {
return ri.offset
}
// 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 (ri *DomainsIterator) Next(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
ri.offset = ri.offset + len(ri.Items)
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 (ri *DomainsIterator) First(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, 0, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
ri.offset = len(ri.Items)
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 (ri *DomainsIterator) Last(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.TotalCount - ri.limit
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.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 (ri *DomainsIterator) Previous(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.offset - (ri.limit * 2)
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
return true
}
func (ri *DomainsIterator) fetch(ctx context.Context, skip, limit int) error {
r := newHTTPRequest(ri.url)
r.setBasicAuth(basicAuthUser, ri.mg.APIKey())
r.setClient(ri.mg.Client())
if skip != 0 {
r.addParameter("skip", strconv.Itoa(skip))
}
if limit != 0 {
r.addParameter("limit", strconv.Itoa(limit))
}
return getResponseFromJSON(ctx, r, &ri.domainsListResponse)
}
// GetDomain retrieves detailed information about the named domain.
func (mg *MailgunImpl) GetDomain(ctx context.Context, domain string) (DomainResponse, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp DomainResponse
err := getResponseFromJSON(ctx, r, &resp)
return resp, err
}
func (mg *MailgunImpl) VerifyDomain(ctx context.Context, domain string) (string, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/verify")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
var resp DomainResponse
err := putResponseFromJSON(ctx, r, payload, &resp)
return resp.Domain.State, err
}
// Optional parameters when creating a domain
type CreateDomainOptions struct {
Password string
SpamAction SpamAction
Wildcard bool
ForceDKIMAuthority bool
DKIMKeySize int
IPS []string
}
// CreateDomain instructs Mailgun to create a new domain for your account.
// The name parameter identifies the domain.
// The smtpPassword parameter provides an access credential for the domain.
// The spamAction domain must be one of Delete, Tag, or Disabled.
// The wildcard parameter instructs Mailgun to treat all subdomains of this domain uniformly if true,
// and as different domains if false.
func (mg *MailgunImpl) CreateDomain(ctx context.Context, name string, opts *CreateDomainOptions) (DomainResponse, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("name", name)
if opts != nil {
if opts.SpamAction != "" {
payload.addValue("spam_action", string(opts.SpamAction))
}
if opts.Wildcard {
payload.addValue("wildcard", boolToString(opts.Wildcard))
}
if opts.ForceDKIMAuthority {
payload.addValue("force_dkim_authority", boolToString(opts.ForceDKIMAuthority))
}
if opts.DKIMKeySize != 0 {
payload.addValue("dkim_key_size", strconv.Itoa(opts.DKIMKeySize))
}
if len(opts.IPS) != 0 {
payload.addValue("ips", strings.Join(opts.IPS, ","))
}
if len(opts.Password) != 0 {
payload.addValue("smtp_password", opts.Password)
}
}
var resp DomainResponse
err := postResponseFromJSON(ctx, r, payload, &resp)
return resp, err
}
// GetDomainConnection returns delivery connection settings for the defined domain
func (mg *MailgunImpl) GetDomainConnection(ctx context.Context, domain string) (DomainConnection, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/connection")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp domainConnectionResponse
err := getResponseFromJSON(ctx, r, &resp)
return resp.Connection, err
}
// Updates the specified delivery connection settings for the defined domain
func (mg *MailgunImpl) UpdateDomainConnection(ctx context.Context, domain string, settings DomainConnection) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/connection")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("require_tls", boolToString(settings.RequireTLS))
payload.addValue("skip_verification", boolToString(settings.SkipVerification))
_, err := makePutRequest(ctx, r, payload)
return err
}
// DeleteDomain instructs Mailgun to dispose of the named domain name
func (mg *MailgunImpl) DeleteDomain(ctx context.Context, name string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + name)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetDomainTracking returns tracking settings for a domain
func (mg *MailgunImpl) GetDomainTracking(ctx context.Context, domain string) (DomainTracking, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/tracking")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp domainTrackingResponse
err := getResponseFromJSON(ctx, r, &resp)
return resp.Tracking, err
}
func (mg *MailgunImpl) UpdateClickTracking(ctx context.Context, domain, active string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/tracking/click")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("active", active)
_, err := makePutRequest(ctx, r, payload)
return err
}
func (mg *MailgunImpl) UpdateUnsubscribeTracking(ctx context.Context, domain, active, htmlFooter, textFooter string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/tracking/unsubscribe")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("active", active)
payload.addValue("html_footer", htmlFooter)
payload.addValue("text_footer", textFooter)
_, err := makePutRequest(ctx, r, payload)
return err
}
func (mg *MailgunImpl) UpdateOpenTracking(ctx context.Context, domain, active string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/tracking/open")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("active", active)
_, err := makePutRequest(ctx, r, payload)
return err
}
func boolToString(b bool) string {
if b {
return "true"
}
return "false"
}

View File

@@ -0,0 +1,215 @@
package mailgun
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"github.com/pkg/errors"
)
// The EmailVerificationParts structure breaks out the basic elements of an email address.
// LocalPart includes everything up to the '@' in an e-mail address.
// Domain includes everything after the '@'.
// DisplayName is no longer used, and will appear as "".
type EmailVerificationParts struct {
LocalPart string `json:"local_part"`
Domain string `json:"domain"`
DisplayName string `json:"display_name"`
}
// EmailVerification records basic facts about a validated e-mail address.
// See the ValidateEmail method and example for more details.
//
type EmailVerification struct {
// Indicates whether an email address conforms to IETF RFC standards.
IsValid bool `json:"is_valid"`
// Indicates whether an email address is deliverable.
MailboxVerification string `json:"mailbox_verification"`
// Parts records the different subfields of the parsed email address
Parts EmailVerificationParts `json:"parts"`
// Echoes the address provided.
Address string `json:"address"`
// Provides a simple recommendation in case the address is invalid or
// Mailgun thinks you might have a typo. May be empty, in which case
// Mailgun has no recommendation to give.
DidYouMean string `json:"did_you_mean"`
// Indicates whether Mailgun thinks the address is from a known
// disposable mailbox provider.
IsDisposableAddress bool `json:"is_disposable_address"`
// Indicates whether Mailgun thinks the address is an email distribution list.
IsRoleAddress bool `json:"is_role_address"`
// The reason why a specific validation may be unsuccessful. (Available in the V3 response)
Reason string `json:"reason"`
// A list of potential reasons why a specific validation may be unsuccessful. (Available in the v4 response)
Reasons []string
}
type v4EmailValidationResp struct {
IsValid bool `json:"is_valid"`
MailboxVerification string `json:"mailbox_verification"`
Parts EmailVerificationParts `json:"parts"`
Address string `json:"address"`
DidYouMean string `json:"did_you_mean"`
IsDisposableAddress bool `json:"is_disposable_address"`
IsRoleAddress bool `json:"is_role_address"`
Reason []string `json:"reason"`
}
type addressParseResult struct {
Parsed []string `json:"parsed"`
Unparseable []string `json:"unparseable"`
}
type EmailValidator interface {
ValidateEmail(ctx context.Context, email string, mailBoxVerify bool) (EmailVerification, error)
ParseAddresses(ctx context.Context, addresses ...string) ([]string, []string, error)
}
type EmailValidatorImpl struct {
client *http.Client
isPublicKey bool
apiBase string
apiKey string
}
// Creates a new validation instance.
// * If a public key is provided, uses the public validation endpoints
// * If a private key is provided, uses the private validation endpoints
func NewEmailValidator(apiKey string) *EmailValidatorImpl {
isPublicKey := false
// Did the user pass in a public key?
if strings.HasPrefix(apiKey, "pubkey-") {
isPublicKey = true
}
return &EmailValidatorImpl{
client: http.DefaultClient,
isPublicKey: isPublicKey,
apiBase: APIBase,
apiKey: apiKey,
}
}
// NewEmailValidatorFromEnv returns a new EmailValidator using environment variables
// If MG_PUBLIC_API_KEY is set, assume using the free validation subject to daily usage limits
// If only MG_API_KEY is set, assume using the /private validation routes with no daily usage limits
func NewEmailValidatorFromEnv() (*EmailValidatorImpl, error) {
apiKey := os.Getenv("MG_PUBLIC_API_KEY")
if apiKey == "" {
apiKey = os.Getenv("MG_API_KEY")
if apiKey == "" {
return nil, errors.New(
"environment variable MG_PUBLIC_API_KEY or MG_API_KEY required for email validation")
}
}
v := NewEmailValidator(apiKey)
url := os.Getenv("MG_URL")
if url != "" {
v.SetAPIBase(url)
}
return v, nil
}
// APIBase returns the API Base URL configured for this client.
func (m *EmailValidatorImpl) APIBase() string {
return m.apiBase
}
// SetAPIBase updates the API Base URL for this client.
func (m *EmailValidatorImpl) SetAPIBase(address string) {
m.apiBase = address
}
// SetClient updates the HTTP client for this client.
func (m *EmailValidatorImpl) SetClient(c *http.Client) {
m.client = c
}
// Client returns the HTTP client configured for this client.
func (m *EmailValidatorImpl) Client() *http.Client {
return m.client
}
// APIKey returns the API key used for validations
func (m *EmailValidatorImpl) APIKey() string {
return m.apiKey
}
func (m *EmailValidatorImpl) getAddressURL(endpoint string) string {
if m.isPublicKey {
return fmt.Sprintf("%s/address/%s", m.APIBase(), endpoint)
}
return fmt.Sprintf("%s/address/private/%s", m.APIBase(), endpoint)
}
// ValidateEmail performs various checks on the email address provided to ensure it's correctly formatted.
// It may also be used to break an email address into its sub-components. If user has set the
func (m *EmailValidatorImpl) ValidateEmail(ctx context.Context, email string, mailBoxVerify bool) (EmailVerification, error) {
if strings.HasSuffix(m.APIBase(), "/v4") {
return m.validateV4(ctx, email, mailBoxVerify)
}
return m.validateV3(ctx, email, mailBoxVerify)
}
func (m *EmailValidatorImpl) validateV3(ctx context.Context, email string, mailBoxVerify bool) (EmailVerification, error) {
r := newHTTPRequest(m.getAddressURL("validate"))
r.setClient(m.Client())
r.addParameter("address", email)
if mailBoxVerify {
r.addParameter("mailbox_verification", "true")
}
r.setBasicAuth(basicAuthUser, m.APIKey())
var res EmailVerification
err := getResponseFromJSON(ctx, r, &res)
if err != nil {
return EmailVerification{}, err
}
return res, nil
}
func (m *EmailValidatorImpl) validateV4(ctx context.Context, email string, mailBoxVerify bool) (EmailVerification, error) {
r := newHTTPRequest(fmt.Sprintf("%s/address/validate", m.APIBase()))
r.setClient(m.Client())
r.addParameter("address", email)
if mailBoxVerify {
r.addParameter("mailbox_verification", "true")
}
r.setBasicAuth(basicAuthUser, m.APIKey())
var res v4EmailValidationResp
err := getResponseFromJSON(ctx, r, &res)
if err != nil {
return EmailVerification{}, err
}
return EmailVerification{
IsValid: res.IsValid,
MailboxVerification: res.MailboxVerification,
Parts: res.Parts,
Address: res.Address,
DidYouMean: res.DidYouMean,
IsDisposableAddress: res.IsDisposableAddress,
IsRoleAddress: res.IsRoleAddress,
Reasons: res.Reason}, nil
}
// ParseAddresses takes a list of addresses and sorts them into valid and invalid address categories.
// NOTE: Use of this function requires a proper public API key. The private API key will not work.
func (m *EmailValidatorImpl) ParseAddresses(ctx context.Context, addresses ...string) ([]string, []string, error) {
r := newHTTPRequest(m.getAddressURL("parse"))
r.setClient(m.Client())
r.addParameter("addresses", strings.Join(addresses, ","))
r.setBasicAuth(basicAuthUser, m.APIKey())
var response addressParseResult
err := getResponseFromJSON(ctx, r, &response)
if err != nil {
return nil, nil, err
}
return response.Parsed, response.Unparseable, nil
}

293
vendor/github.com/mailgun/mailgun-go/v4/events.go generated vendored Normal file
View File

@@ -0,0 +1,293 @@
package mailgun
import (
"context"
"fmt"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/mailgun/mailgun-go/v4/events"
)
// ListEventOptions{} modifies the behavior of ListEvents()
type ListEventOptions struct {
// Limits the results to a specific start and end time
Begin, End time.Time
// ForceAscending and ForceDescending are used to force Mailgun to use a given
// traversal order of the events. If both ForceAscending and ForceDescending are
// true, an error will result. If none, the default will be inferred from the Begin
// and End parameters.
ForceAscending, ForceDescending bool
// Compact, if true, compacts the returned JSON to minimize transmission bandwidth.
Compact bool
// Limit caps the number of results returned. If left unspecified, MailGun assumes 100.
Limit int
// Filter allows the caller to provide more specialized filters on the query.
// Consult the Mailgun documentation for more details.
Filter map[string]string
PollInterval time.Duration
}
// EventIterator maintains the state necessary for paging though small parcels of a larger set of events.
type EventIterator struct {
events.Response
mg Mailgun
err error
}
// Create an new iterator to fetch a page of events from the events api with a specific domain
func (mg *MailgunImpl) ListEventsWithDomain(opts *ListEventOptions, domain string) *EventIterator {
url := generateApiUrlWithDomain(mg, eventsEndpoint, domain)
return mg.listEvents(url, opts)
}
// Create an new iterator to fetch a page of events from the events api
func (mg *MailgunImpl) ListEvents(opts *ListEventOptions) *EventIterator {
url := generateApiUrl(mg, eventsEndpoint)
return mg.listEvents(url, opts)
}
func (mg *MailgunImpl) listEvents(url string, opts *ListEventOptions) *EventIterator {
req := newHTTPRequest(url)
if opts != nil {
if opts.Limit > 0 {
req.addParameter("limit", fmt.Sprintf("%d", opts.Limit))
}
if opts.Compact {
req.addParameter("pretty", "no")
}
if opts.ForceAscending {
req.addParameter("ascending", "yes")
} else if opts.ForceDescending {
req.addParameter("ascending", "no")
}
if !opts.Begin.IsZero() {
req.addParameter("begin", formatMailgunTime(opts.Begin))
}
if !opts.End.IsZero() {
req.addParameter("end", formatMailgunTime(opts.End))
}
if opts.Filter != nil {
for k, v := range opts.Filter {
req.addParameter(k, v)
}
}
}
url, err := req.generateUrlWithParameters()
return &EventIterator{
mg: mg,
Response: events.Response{Paging: events.Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (ei *EventIterator) Err() error {
return ei.err
}
// Next retrieves the next page of events 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 (ei *EventIterator) Next(ctx context.Context, events *[]Event) bool {
if ei.err != nil {
return false
}
ei.err = ei.fetch(ctx, ei.Paging.Next)
if ei.err != nil {
return false
}
*events, ei.err = ParseEvents(ei.Items)
if ei.err != nil {
return false
}
if len(ei.Items) == 0 {
return false
}
return true
}
// First retrieves the first page of events 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 (ei *EventIterator) First(ctx context.Context, events *[]Event) bool {
if ei.err != nil {
return false
}
ei.err = ei.fetch(ctx, ei.Paging.First)
if ei.err != nil {
return false
}
*events, ei.err = ParseEvents(ei.Items)
return true
}
// Last retrieves the last page of events 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 (ei *EventIterator) Last(ctx context.Context, events *[]Event) bool {
if ei.err != nil {
return false
}
ei.err = ei.fetch(ctx, ei.Paging.Last)
if ei.err != nil {
return false
}
*events, ei.err = ParseEvents(ei.Items)
return true
}
// Previous retrieves the previous page of events 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 (ei *EventIterator) Previous(ctx context.Context, events *[]Event) bool {
if ei.err != nil {
return false
}
if ei.Paging.Previous == "" {
return false
}
ei.err = ei.fetch(ctx, ei.Paging.Previous)
if ei.err != nil {
return false
}
*events, ei.err = ParseEvents(ei.Items)
if len(ei.Items) == 0 {
return false
}
return true
}
func (ei *EventIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(ei.mg.Client())
r.setBasicAuth(basicAuthUser, ei.mg.APIKey())
resp, err := makeRequest(ctx, r, "GET", nil)
if err != nil {
return err
}
if err := jsoniter.Unmarshal(resp.Data, &ei.Response); err != nil {
return fmt.Errorf("failed to un-marshall event.Response: %s", err)
}
return nil
}
// EventPoller maintains the state necessary for polling events
type EventPoller struct {
it *EventIterator
opts ListEventOptions
thresholdTime time.Time
beginTime time.Time
sleepUntil time.Time
mg Mailgun
err error
}
// Poll the events api and return new events as they occur
// it = mg.PollEvents(&ListEventOptions{
// // Only events with a timestamp after this date/time will be returned
// Begin: time.Now().Add(time.Second * -3),
// // How often we poll the api for new events
// PollInterval: time.Second * 4
// })
//
// var events []Event
// ctx, cancel := context.WithCancel(context.Background())
//
// // Blocks until new events appear or context is cancelled
// for it.Poll(ctx, &events) {
// for _, event := range(events) {
// fmt.Printf("Event %+v\n", event)
// }
// }
// if it.Err() != nil {
// log.Fatal(it.Err())
// }
func (mg *MailgunImpl) PollEvents(opts *ListEventOptions) *EventPoller {
now := time.Now()
// ForceAscending must be set
opts.ForceAscending = true
// Default begin time is 30 minutes ago
if opts.Begin.IsZero() {
opts.Begin = now.Add(time.Minute * -30)
}
// Set a 15 second poll interval if none set
if opts.PollInterval.Nanoseconds() == 0 {
opts.PollInterval = time.Duration(time.Second * 15)
}
return &EventPoller{
it: mg.ListEvents(opts),
opts: *opts,
mg: mg,
}
}
// If an error occurred during polling `Err()` will return non nil
func (ep *EventPoller) Err() error {
return ep.err
}
func (ep *EventPoller) Poll(ctx context.Context, events *[]Event) bool {
var currentPage string
var results []Event
if ep.opts.Begin.IsZero() {
ep.beginTime = time.Now().UTC()
}
for {
// Remember our current page url
currentPage = ep.it.Paging.Next
// Attempt to get a page of events
var page []Event
if ep.it.Next(ctx, &page) == false {
if ep.it.Err() == nil && len(page) == 0 {
// No events, sleep for our poll interval
goto SLEEP
}
ep.err = ep.it.Err()
return false
}
for _, e := range page {
// If any events on the page are older than our being time
if e.GetTimestamp().After(ep.beginTime) {
results = append(results, e)
}
}
// If we have events to return
if len(results) != 0 {
*events = results
results = nil
return true
}
SLEEP:
// Since we didn't find an event older than our
// threshold, fetch this same page again
ep.it.Paging.Next = currentPage
// Sleep the rest of our duration
tick := time.NewTicker(ep.opts.PollInterval)
select {
case <-ctx.Done():
return false
case <-tick.C:
tick.Stop()
}
}
}
// Given time.Time{} return a float64 as given in mailgun event timestamps
func TimeToFloat(t time.Time) float64 {
return float64(t.Unix()) + (float64(t.Nanosecond()/int(time.Microsecond)) / float64(1000000))
}

View File

@@ -0,0 +1,57 @@
package events
const (
EventAccepted = "accepted"
EventRejected = "rejected"
EventDelivered = "delivered"
EventFailed = "failed"
EventOpened = "opened"
EventClicked = "clicked"
EventUnsubscribed = "unsubscribed"
EventComplained = "complained"
EventStored = "stored"
EventDropped = "dropped"
EventListMemberUploaded = "list_member_uploaded"
EventListMemberUploadError = "list_member_upload_error"
EventListUploaded = "list_uploaded"
)
const (
TransportHTTP = "http"
TransportSMTP = "smtp"
DeviceUnknown = "unknown"
DeviceMobileBrowser = "desktop"
DeviceBrowser = "mobile"
DeviceEmail = "tablet"
DeviceOther = "other"
ClientUnknown = "unknown"
ClientMobileBrowser = "mobile browser"
ClientBrowser = "browser"
ClientEmail = "email client"
ClientLibrary = "library"
ClientRobot = "robot"
ClientOther = "other"
ReasonUnknown = "unknown"
ReasonGeneric = "generic"
ReasonBounce = "bounce"
ReasonESPBlock = "espblock"
ReasonGreylisted = "greylisted"
ReasonBlacklisted = "blacklisted"
ReasonSuppressBounce = "suppress-bounce"
ReasonSuppressComplaint = "suppress-complaint"
ReasonSuppressUnsubscribe = "suppress-unsubscribe"
ReasonOld = "old"
ReasonHardFail = "hardfail"
SeverityUnknown = "unknown"
SeverityTemporary = "temporary"
SeverityPermanent = "permanent"
SeverityInternal = "internal"
MethodUnknown = "unknown"
MethodSMTP = "smtp"
MethodHTTP = "http"
)

View File

@@ -0,0 +1,265 @@
package events
import (
"strings"
"time"
)
// An EventName is a struct with the event name.
type EventName struct {
Name string `json:"event"`
}
// GetName returns the name of the event.
func (e *EventName) GetName() string {
return strings.ToLower(e.Name)
}
func (e *EventName) SetName(name string) {
e.Name = strings.ToLower(name)
}
type Generic struct {
EventName
Timestamp float64 `json:"timestamp"`
ID string `json:"id"`
}
func (g *Generic) GetTimestamp() time.Time {
return time.Unix(0, int64(g.Timestamp*float64(time.Second))).UTC()
}
func (g *Generic) SetTimestamp(t time.Time) {
// convert := fmt.Sprintf("%d.%06d", t.Unix(), t.Nanosecond()/int(time.Microsecond))
// ts, err := strconv.ParseFloat(convert, 64)
g.Timestamp = float64(t.Unix()) + (float64(t.Nanosecond()/int(time.Microsecond)) / float64(1000000))
}
func (g *Generic) GetID() string {
return g.ID
}
func (g *Generic) SetID(id string) {
g.ID = id
}
//
// Message Events
//
type Accepted struct {
Generic
Envelope Envelope `json:"envelope"`
Message Message `json:"message"`
Flags Flags `json:"flags"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Method string `json:"method"`
OriginatingIP string `json:"originating-ip"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
UserVariables interface{} `json:"user-variables"`
Storage Storage `json:"storage"`
}
type Rejected struct {
Generic
Reject struct {
Reason string `json:"reason"`
Description string `json:"description"`
} `json:"reject"`
Message Message `json:"message"`
Storage Storage `json:"storage"`
Flags Flags `json:"flags"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
UserVariables interface{} `json:"user-variables"`
}
type Delivered struct {
Generic
Envelope Envelope `json:"envelope"`
Message Message `json:"message"`
Flags Flags `json:"flags"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Method string `json:"method"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
Storage Storage `json:"storage"`
DeliveryStatus DeliveryStatus `json:"delivery-status"`
UserVariables interface{} `json:"user-variables"`
}
type Failed struct {
Generic
Envelope Envelope `json:"envelope"`
Message Message `json:"message"`
Flags Flags `json:"flags"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Method string `json:"method"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
Storage Storage `json:"storage"`
DeliveryStatus DeliveryStatus `json:"delivery-status"`
Severity string `json:"severity"`
Reason string `json:"reason"`
UserVariables interface{} `json:"user-variables"`
}
type Stored struct {
Generic
Message Message `json:"message"`
Storage Storage `json:"storage"`
Flags Flags `json:"flags"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
UserVariables interface{} `json:"user-variables"`
}
//
// Message Events (User)
//
type Opened struct {
Generic
Message Message `json:"message"`
Campaigns []Campaign `json:"campaigns"`
MailingList MailingList `json:"mailing-list"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Tags []string `json:"tags"`
IP string `json:"ip"`
ClientInfo ClientInfo `json:"client-info"`
GeoLocation GeoLocation `json:"geolocation"`
UserVariables interface{} `json:"user-variables"`
}
type Clicked struct {
Generic
Url string `json:"url"`
Message Message `json:"message"`
Campaigns []Campaign `json:"campaigns"`
MailingList MailingList `json:"mailing-list"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Tags []string `json:"tags"`
IP string `json:"ip"`
ClientInfo ClientInfo `json:"client-info"`
GeoLocation GeoLocation `json:"geolocation"`
UserVariables interface{} `json:"user-variables"`
}
type Unsubscribed struct {
Generic
Message Message `json:"message"`
Campaigns []Campaign `json:"campaigns"`
MailingList MailingList `json:"mailing-list"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Tags []string `json:"tags"`
IP string `json:"ip"`
ClientInfo ClientInfo `json:"client-info"`
GeoLocation GeoLocation `json:"geolocation"`
UserVariables interface{} `json:"user-variables"`
}
type Complained struct {
Generic
Message Message `json:"message"`
Campaigns []Campaign `json:"campaigns"`
Recipient string `json:"recipient"`
Tags []string `json:"tags"`
UserVariables interface{} `json:"user-variables"`
}
//
// Mailing List Events
//
type MailingListMember struct {
Subscribed bool
Address string
Name string
Vars []string
}
type MailingListError struct {
Message string
}
type ListMemberUploaded struct {
Generic
MailingList MailingList `json:"mailing-list"`
Member MailingListMember `json:"member"`
TaskID string `json:"task-id"`
}
type ListMemberUploadError struct {
Generic
MailingList MailingList `json:"mailing-list"`
TaskID string `json:"task-id"`
Format string `json:"format"`
MemberDescription string `json:"member-description"`
Error MailingListError `json:"error"`
}
type ListUploaded struct {
Generic
MailingList MailingList `json:"mailing-list"`
IsUpsert bool `json:"is-upsert"`
Format string `json:"format"`
UpsertedCount int `json:"upserted-count"`
FailedCount int `json:"failed-count"`
Member MailingListMember `json:"member"`
Subscribed bool `json:"subscribed"`
TaskID string `json:"task-id"`
}
type Paging struct {
First string `json:"first,omitempty"`
Next string `json:"next,omitempty"`
Previous string `json:"previous,omitempty"`
Last string `json:"last,omitempty"`
}
type RawJSON []byte
func (v *RawJSON) UnmarshalJSON(data []byte) error {
*v = data
return nil
}
type Response struct {
Items []RawJSON `json:"items"`
Paging Paging `json:"paging"`
}

View File

@@ -0,0 +1,78 @@
package events
type ClientInfo struct {
AcceptLanguage string `json:"accept-language"`
ClientName string `json:"client-name"`
ClientOS string `json:"client-os"`
ClientType string `json:"client-type"`
DeviceType string `json:"device-type"`
IP string `json:"ip"`
UserAgent string `json:"user-agent"`
}
type GeoLocation struct {
City string `json:"city"`
Country string `json:"country"`
Region string `json:"region"`
}
type MailingList struct {
Address string `json:"address"`
ListID string `json:"list-id"`
SID string `json:"sid"`
}
type Message struct {
Headers MessageHeaders `json:"headers"`
Attachments []Attachment `json:"attachments"`
Recipients []string `json:"recipients"`
Size int `json:"size"`
}
type Envelope struct {
MailFrom string `json:"mail-from"`
Sender string `json:"sender"`
Transport string `json:"transport"`
Targets string `json:"targets"`
SendingHost string `json:"sending-host"`
SendingIP string `json:"sending-ip"`
}
type Storage struct {
Key string `json:"key"`
URL string `json:"url"`
}
type Flags struct {
IsAuthenticated bool `json:"is-authenticated"`
IsBig bool `json:"is-big"`
IsSystemTest bool `json:"is-system-test"`
IsTestMode bool `json:"is-test-mode"`
IsDelayedBounce bool `json:"is-delayed-bounce"`
}
type Attachment struct {
FileName string `json:"filename"`
ContentType string `json:"content-type"`
Size int `json:"size"`
}
type MessageHeaders struct {
To string `json:"to"`
MessageID string `json:"message-id"`
From string `json:"from"`
Subject string `json:"subject"`
}
type Campaign struct {
ID string `json:"id"`
Name string `json:"name"`
}
type DeliveryStatus struct {
Code int `json:"code"`
AttemptNo int `json:"attempt-no"`
Description string `json:"description"`
Message string `json:"message"`
SessionSeconds float64 `json:"session-seconds"`
}

99
vendor/github.com/mailgun/mailgun-go/v4/exports.go generated vendored Normal file
View File

@@ -0,0 +1,99 @@
package mailgun
import (
"context"
"errors"
"fmt"
"net/http"
)
type ExportList struct {
Items []Export `json:"items"`
}
type Export struct {
ID string `json:"id"`
Status string `json:"status"`
URL string `json:"url"`
}
// Create an export based on the URL given
func (mg *MailgunImpl) CreateExport(ctx context.Context, url string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, exportsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("url", url)
_, err := makePostRequest(ctx, r, payload)
return err
}
// List all exports created within the past 24 hours
func (mg *MailgunImpl) ListExports(ctx context.Context, url string) ([]Export, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, exportsEndpoint))
r.setClient(mg.Client())
if url != "" {
r.addParameter("url", url)
}
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp ExportList
if err := getResponseFromJSON(ctx, r, &resp); err != nil {
return nil, err
}
var result []Export
for _, item := range resp.Items {
result = append(result, Export(item))
}
return result, nil
}
// GetExport gets an export by id
func (mg *MailgunImpl) GetExport(ctx context.Context, id string) (Export, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, exportsEndpoint) + "/" + id)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp Export
err := getResponseFromJSON(ctx, r, &resp)
return resp, err
}
// Download an export by ID. This will respond with a '302 Moved'
// with the Location header of temporary S3 URL if it is available.
func (mg *MailgunImpl) GetExportLink(ctx context.Context, id string) (string, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, exportsEndpoint) + "/" + id + "/download_url")
c := mg.Client()
// Ensure the client doesn't attempt to retry
c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return errors.New("redirect")
}
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addHeader("User-Agent", MailgunGoUserAgent)
req, err := r.NewRequest(ctx, "GET", nil)
if err != nil {
return "", err
}
if Debug {
fmt.Println(r.curlString(req, nil))
}
resp, err := r.Client.Do(req)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusFound {
url, err := resp.Location()
if err != nil {
return "", fmt.Errorf("while parsing 302 redirect url: %s", err)
}
return url.String(), nil
}
return "", err
}
return "", fmt.Errorf("expected a 302 response, API returned a '%d' instead", resp.StatusCode)
}

12
vendor/github.com/mailgun/mailgun-go/v4/go.mod generated vendored Normal file
View File

@@ -0,0 +1,12 @@
module github.com/mailgun/mailgun-go/v4
go 1.13
require (
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/go-chi/chi v4.0.0+incompatible
github.com/json-iterator/go v1.1.10
github.com/pkg/errors v0.8.1
)

23
vendor/github.com/mailgun/mailgun-go/v4/go.sum generated vendored Normal file
View File

@@ -0,0 +1,23 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuYrgaRcnW4=
github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

330
vendor/github.com/mailgun/mailgun-go/v4/httphelpers.go generated vendored Normal file
View File

@@ -0,0 +1,330 @@
package mailgun
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"os"
"path"
"regexp"
"strings"
)
var validURL = regexp.MustCompile(`^/v[2-4].*`)
type httpRequest struct {
URL string
Parameters map[string][]string
Headers map[string]string
BasicAuthUser string
BasicAuthPassword string
Client *http.Client
}
type httpResponse struct {
Code int
Data []byte
}
type payload interface {
getPayloadBuffer() (*bytes.Buffer, error)
getContentType() string
getValues() []keyValuePair
}
type keyValuePair struct {
key string
value string
}
type keyNameRC struct {
key string
name string
value io.ReadCloser
}
type keyNameBuff struct {
key string
name string
value []byte
}
type formDataPayload struct {
contentType string
Values []keyValuePair
Files []keyValuePair
ReadClosers []keyNameRC
Buffers []keyNameBuff
}
type urlEncodedPayload struct {
Values []keyValuePair
}
func newHTTPRequest(url string) *httpRequest {
return &httpRequest{URL: url, Client: http.DefaultClient}
}
func (r *httpRequest) addParameter(name, value string) {
if r.Parameters == nil {
r.Parameters = make(map[string][]string)
}
r.Parameters[name] = append(r.Parameters[name], value)
}
func (r *httpRequest) setClient(c *http.Client) {
r.Client = c
}
func (r *httpRequest) setBasicAuth(user, password string) {
r.BasicAuthUser = user
r.BasicAuthPassword = password
}
func newUrlEncodedPayload() *urlEncodedPayload {
return &urlEncodedPayload{}
}
func (f *urlEncodedPayload) addValue(key, value string) {
f.Values = append(f.Values, keyValuePair{key: key, value: value})
}
func (f *urlEncodedPayload) getPayloadBuffer() (*bytes.Buffer, error) {
data := url.Values{}
for _, keyVal := range f.Values {
data.Add(keyVal.key, keyVal.value)
}
return bytes.NewBufferString(data.Encode()), nil
}
func (f *urlEncodedPayload) getContentType() string {
return "application/x-www-form-urlencoded"
}
func (f *urlEncodedPayload) getValues() []keyValuePair {
return f.Values
}
func (r *httpResponse) parseFromJSON(v interface{}) error {
return json.Unmarshal(r.Data, v)
}
func newFormDataPayload() *formDataPayload {
return &formDataPayload{}
}
func (f *formDataPayload) getValues() []keyValuePair {
return f.Values
}
func (f *formDataPayload) addValue(key, value string) {
f.Values = append(f.Values, keyValuePair{key: key, value: value})
}
func (f *formDataPayload) addFile(key, file string) {
f.Files = append(f.Files, keyValuePair{key: key, value: file})
}
func (f *formDataPayload) addBuffer(key, file string, buff []byte) {
f.Buffers = append(f.Buffers, keyNameBuff{key: key, name: file, value: buff})
}
func (f *formDataPayload) addReadCloser(key, name string, rc io.ReadCloser) {
f.ReadClosers = append(f.ReadClosers, keyNameRC{key: key, name: name, value: rc})
}
func (f *formDataPayload) getPayloadBuffer() (*bytes.Buffer, error) {
data := &bytes.Buffer{}
writer := multipart.NewWriter(data)
defer writer.Close()
for _, keyVal := range f.Values {
if tmp, err := writer.CreateFormField(keyVal.key); err == nil {
tmp.Write([]byte(keyVal.value))
} else {
return nil, err
}
}
for _, file := range f.Files {
if tmp, err := writer.CreateFormFile(file.key, path.Base(file.value)); err == nil {
if fp, err := os.Open(file.value); err == nil {
defer fp.Close()
io.Copy(tmp, fp)
} else {
return nil, err
}
} else {
return nil, err
}
}
for _, file := range f.ReadClosers {
if tmp, err := writer.CreateFormFile(file.key, file.name); err == nil {
defer file.value.Close()
io.Copy(tmp, file.value)
} else {
return nil, err
}
}
for _, buff := range f.Buffers {
if tmp, err := writer.CreateFormFile(buff.key, buff.name); err == nil {
r := bytes.NewReader(buff.value)
io.Copy(tmp, r)
} else {
return nil, err
}
}
f.contentType = writer.FormDataContentType()
return data, nil
}
func (f *formDataPayload) getContentType() string {
if f.contentType == "" {
f.getPayloadBuffer()
}
return f.contentType
}
func (r *httpRequest) addHeader(name, value string) {
if r.Headers == nil {
r.Headers = make(map[string]string)
}
r.Headers[name] = value
}
func (r *httpRequest) makeGetRequest(ctx context.Context) (*httpResponse, error) {
return r.makeRequest(ctx, "GET", nil)
}
func (r *httpRequest) makePostRequest(ctx context.Context, payload payload) (*httpResponse, error) {
return r.makeRequest(ctx, "POST", payload)
}
func (r *httpRequest) makePutRequest(ctx context.Context, payload payload) (*httpResponse, error) {
return r.makeRequest(ctx, "PUT", payload)
}
func (r *httpRequest) makeDeleteRequest(ctx context.Context) (*httpResponse, error) {
return r.makeRequest(ctx, "DELETE", nil)
}
func (r *httpRequest) NewRequest(ctx context.Context, method string, payload payload) (*http.Request, error) {
url, err := r.generateUrlWithParameters()
if err != nil {
return nil, err
}
var body io.Reader
if payload != nil {
if body, err = payload.getPayloadBuffer(); err != nil {
return nil, err
}
} else {
body = nil
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if payload != nil && payload.getContentType() != "" {
req.Header.Add("Content-Type", payload.getContentType())
}
if r.BasicAuthUser != "" && r.BasicAuthPassword != "" {
req.SetBasicAuth(r.BasicAuthUser, r.BasicAuthPassword)
}
for header, value := range r.Headers {
req.Header.Add(header, value)
}
return req, nil
}
func (r *httpRequest) makeRequest(ctx context.Context, method string, payload payload) (*httpResponse, error) {
req, err := r.NewRequest(ctx, method, payload)
if err != nil {
return nil, err
}
if Debug {
fmt.Println(r.curlString(req, payload))
}
response := httpResponse{}
resp, err := r.Client.Do(req)
if resp != nil {
response.Code = resp.StatusCode
}
if err != nil {
if urlErr, ok := err.(*url.Error); ok {
if urlErr.Err == io.EOF {
return nil, errors.Wrap(err, "remote server prematurely closed connection")
}
}
return nil, errors.Wrap(err, "while making http request")
}
defer resp.Body.Close()
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "while reading response body")
}
response.Data = responseBody
return &response, nil
}
func (r *httpRequest) generateUrlWithParameters() (string, error) {
url, err := url.Parse(r.URL)
if err != nil {
return "", err
}
if !validURL.MatchString(url.Path) {
return "", errors.New(`BaseAPI must end with a /v2, /v3 or /v4; setBaseAPI("https://host/v3")`)
}
q := url.Query()
if r.Parameters != nil && len(r.Parameters) > 0 {
for name, values := range r.Parameters {
for _, value := range values {
q.Add(name, value)
}
}
}
url.RawQuery = q.Encode()
return url.String(), nil
}
func (r *httpRequest) curlString(req *http.Request, p payload) string {
parts := []string{"curl", "-i", "-X", req.Method, req.URL.String()}
for key, value := range req.Header {
parts = append(parts, fmt.Sprintf("-H \"%s: %s\"", key, value[0]))
}
//parts = append(parts, fmt.Sprintf(" --user '%s:%s'", r.BasicAuthUser, r.BasicAuthPassword))
if p != nil {
for _, param := range p.getValues() {
parts = append(parts, fmt.Sprintf(" -F %s='%s'", param.key, param.value))
}
}
return strings.Join(parts, " ")
}

87
vendor/github.com/mailgun/mailgun-go/v4/ips.go generated vendored Normal file
View File

@@ -0,0 +1,87 @@
package mailgun
import "context"
type ipAddressListResponse struct {
TotalCount int `json:"total_count"`
Items []string `json:"items"`
}
type IPAddress struct {
IP string `json:"ip"`
RDNS string `json:"rdns"`
Dedicated bool `json:"dedicated"`
}
type okResp struct {
ID string `json:"id,omitempty"`
Message string `json:"message"`
}
// ListIPS returns a list of IPs assigned to your account
func (mg *MailgunImpl) ListIPS(ctx context.Context, dedicated bool) ([]IPAddress, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, ipsEndpoint))
r.setClient(mg.Client())
if dedicated {
r.addParameter("dedicated", "true")
}
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp ipAddressListResponse
if err := getResponseFromJSON(ctx, r, &resp); err != nil {
return nil, err
}
var result []IPAddress
for _, ip := range resp.Items {
result = append(result, IPAddress{IP: ip})
}
return result, nil
}
// GetIP returns information about the specified IP
func (mg *MailgunImpl) GetIP(ctx context.Context, ip string) (IPAddress, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, ipsEndpoint) + "/" + ip)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp IPAddress
err := getResponseFromJSON(ctx, r, &resp)
return resp, err
}
// ListDomainIPS returns a list of IPs currently assigned to the specified domain.
func (mg *MailgunImpl) ListDomainIPS(ctx context.Context) ([]IPAddress, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + mg.domain + "/ips")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp ipAddressListResponse
if err := getResponseFromJSON(ctx, r, &resp); err != nil {
return nil, err
}
var result []IPAddress
for _, ip := range resp.Items {
result = append(result, IPAddress{IP: ip})
}
return result, nil
}
// Assign a dedicated IP to the domain specified.
func (mg *MailgunImpl) AddDomainIP(ctx context.Context, ip string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + mg.domain + "/ips")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("ip", ip)
_, err := makePostRequest(ctx, r, payload)
return err
}
// Unassign an IP from the domain specified.
func (mg *MailgunImpl) DeleteDomainIP(ctx context.Context, ip string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + mg.domain + "/ips/" + ip)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}

18
vendor/github.com/mailgun/mailgun-go/v4/limits.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
package mailgun
import "context"
type TagLimits struct {
Limit int `json:"limit"`
Count int `json:"count"`
}
// GetTagLimits returns tracking settings for a domain
func (mg *MailgunImpl) GetTagLimits(ctx context.Context, domain string) (TagLimits, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/limits/tag")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp TagLimits
err := getResponseFromJSON(ctx, r, &resp)
return resp, err
}

394
vendor/github.com/mailgun/mailgun-go/v4/mailgun.go generated vendored Normal file
View File

@@ -0,0 +1,394 @@
// Package mailgun provides methods for interacting with the Mailgun API. It
// automates the HTTP request/response cycle, encodings, and other details
// needed by the API. This SDK lets you do everything the API lets you, in a
// more Go-friendly way.
//
// For further information please see the Mailgun documentation at
// http://documentation.mailgun.com/
//
// Original Author: Michael Banzon
// Contributions: Samuel A. Falvo II <sam.falvo %at% rackspace.com>
// Derrick J. Wippler <thrawn01 %at% gmail.com>
//
// Examples
//
// All functions and method have a corresponding test, so if you don't find an
// example for a function you'd like to know more about, please check for a
// corresponding test. Of course, contributions to the documentation are always
// welcome as well. Feel free to submit a pull request or open a Github issue
// if you cannot find an example to suit your needs.
//
// List iterators
//
// Most methods that begin with `List` return an iterator which simplfies
// paging through large result sets returned by the mailgun API. Most `List`
// methods allow you to specify a `Limit` parameter which as you'd expect,
// limits the number of items returned per page. Note that, at present,
// Mailgun imposes its own cap of 100 items per page, for all API endpoints.
//
// For example, the following iterates over all pages of events 100 items at a time
//
// mg := mailgun.NewMailgun("your-domain.com", "your-api-key")
// it := mg.ListEvents(&mailgun.ListEventOptions{Limit: 100})
//
// // The entire operation should not take longer than 30 seconds
// ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
// defer cancel()
//
// // For each page of 100 events
// var page []mailgun.Event
// for it.Next(ctx, &page) {
// for _, e := range page {
// // Do something with 'e'
// }
// }
//
//
// License
//
// Copyright (c) 2013-2019, Michael Banzon.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// * Neither the names of Mailgun, Michael Banzon, nor the names of their
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package mailgun
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"time"
)
// Set true to write the HTTP requests in curl for to stdout
var Debug = false
const (
// Base Url the library uses to contact mailgun. Use SetAPIBase() to override
APIBase = "https://api.mailgun.net/v3"
APIBaseUS = APIBase
APIBaseEU = "https://api.eu.mailgun.net/v3"
messagesEndpoint = "messages"
mimeMessagesEndpoint = "messages.mime"
bouncesEndpoint = "bounces"
statsTotalEndpoint = "stats/total"
domainsEndpoint = "domains"
tagsEndpoint = "tags"
eventsEndpoint = "events"
unsubscribesEndpoint = "unsubscribes"
routesEndpoint = "routes"
ipsEndpoint = "ips"
exportsEndpoint = "exports"
webhooksEndpoint = "webhooks"
listsEndpoint = "lists"
basicAuthUser = "api"
templatesEndpoint = "templates"
)
// Mailgun defines the supported subset of the Mailgun API.
// The Mailgun API may contain additional features which have been deprecated since writing this SDK.
// This SDK only covers currently supported interface endpoints.
//
// Note that Mailgun reserves the right to deprecate endpoints.
// Some endpoints listed in this interface may, at any time, become obsolete.
// Always double-check with the Mailgun API Documentation to
// determine the currently supported feature set.
type Mailgun interface {
APIBase() string
Domain() string
APIKey() string
Client() *http.Client
SetClient(client *http.Client)
SetAPIBase(url string)
Send(ctx context.Context, m *Message) (string, string, error)
ReSend(ctx context.Context, id string, recipients ...string) (string, string, error)
NewMessage(from, subject, text string, to ...string) *Message
NewMIMEMessage(body io.ReadCloser, to ...string) *Message
ListBounces(opts *ListOptions) *BouncesIterator
GetBounce(ctx context.Context, address string) (Bounce, error)
AddBounce(ctx context.Context, address, code, err string) error
DeleteBounce(ctx context.Context, address string) error
DeleteBounceList(ctx context.Context) error
GetStats(ctx context.Context, events []string, opts *GetStatOptions) ([]Stats, error)
GetTag(ctx context.Context, tag string) (Tag, error)
DeleteTag(ctx context.Context, tag string) error
ListTags(*ListTagOptions) *TagIterator
ListDomains(opts *ListOptions) *DomainsIterator
GetDomain(ctx context.Context, domain string) (DomainResponse, error)
CreateDomain(ctx context.Context, name string, opts *CreateDomainOptions) (DomainResponse, error)
DeleteDomain(ctx context.Context, name string) error
VerifyDomain(ctx context.Context, name string) (string, error)
UpdateDomainConnection(ctx context.Context, domain string, dc DomainConnection) error
GetDomainConnection(ctx context.Context, domain string) (DomainConnection, error)
GetDomainTracking(ctx context.Context, domain string) (DomainTracking, error)
UpdateClickTracking(ctx context.Context, domain, active string) error
UpdateUnsubscribeTracking(ctx context.Context, domain, active, htmlFooter, textFooter string) error
UpdateOpenTracking(ctx context.Context, domain, active string) error
GetStoredMessage(ctx context.Context, url string) (StoredMessage, error)
GetStoredMessageRaw(ctx context.Context, id string) (StoredMessageRaw, error)
GetStoredAttachment(ctx context.Context, url string) ([]byte, error)
// Deprecated
GetStoredMessageForURL(ctx context.Context, url string) (StoredMessage, error)
// Deprecated
GetStoredMessageRawForURL(ctx context.Context, url string) (StoredMessageRaw, error)
ListCredentials(opts *ListOptions) *CredentialsIterator
CreateCredential(ctx context.Context, login, password string) error
ChangeCredentialPassword(ctx context.Context, login, password string) error
DeleteCredential(ctx context.Context, login string) error
ListUnsubscribes(opts *ListOptions) *UnsubscribesIterator
GetUnsubscribe(ctx context.Context, address string) (Unsubscribe, error)
CreateUnsubscribe(ctx context.Context, address, tag string) error
DeleteUnsubscribe(ctx context.Context, address string) error
DeleteUnsubscribeWithTag(ctx context.Context, a, t string) error
ListComplaints(opts *ListOptions) *ComplaintsIterator
GetComplaint(ctx context.Context, address string) (Complaint, error)
CreateComplaint(ctx context.Context, address string) error
DeleteComplaint(ctx context.Context, address string) error
ListRoutes(opts *ListOptions) *RoutesIterator
GetRoute(ctx context.Context, address string) (Route, error)
CreateRoute(ctx context.Context, address Route) (Route, error)
DeleteRoute(ctx context.Context, address string) error
UpdateRoute(ctx context.Context, address string, r Route) (Route, error)
ListWebhooks(ctx context.Context) (map[string][]string, error)
CreateWebhook(ctx context.Context, kind string, url []string) error
DeleteWebhook(ctx context.Context, kind string) error
GetWebhook(ctx context.Context, kind string) ([]string, error)
UpdateWebhook(ctx context.Context, kind string, url []string) error
VerifyWebhookRequest(req *http.Request) (verified bool, err error)
VerifyWebhookSignature(sig Signature) (verified bool, err error)
ListMailingLists(opts *ListOptions) *ListsIterator
CreateMailingList(ctx context.Context, address MailingList) (MailingList, error)
DeleteMailingList(ctx context.Context, address string) error
GetMailingList(ctx context.Context, address string) (MailingList, error)
UpdateMailingList(ctx context.Context, address string, ml MailingList) (MailingList, error)
ListMembers(address string, opts *ListOptions) *MemberListIterator
GetMember(ctx context.Context, MemberAddr, listAddr string) (Member, error)
CreateMember(ctx context.Context, merge bool, addr string, prototype Member) error
CreateMemberList(ctx context.Context, subscribed *bool, addr string, newMembers []interface{}) error
UpdateMember(ctx context.Context, Member, list string, prototype Member) (Member, error)
DeleteMember(ctx context.Context, Member, list string) error
ListEventsWithDomain(opts *ListEventOptions, domain string) *EventIterator
ListEvents(*ListEventOptions) *EventIterator
PollEvents(*ListEventOptions) *EventPoller
ListIPS(ctx context.Context, dedicated bool) ([]IPAddress, error)
GetIP(ctx context.Context, ip string) (IPAddress, error)
ListDomainIPS(ctx context.Context) ([]IPAddress, error)
AddDomainIP(ctx context.Context, ip string) error
DeleteDomainIP(ctx context.Context, ip string) error
ListExports(ctx context.Context, url string) ([]Export, error)
GetExport(ctx context.Context, id string) (Export, error)
GetExportLink(ctx context.Context, id string) (string, error)
CreateExport(ctx context.Context, url string) error
GetTagLimits(ctx context.Context, domain string) (TagLimits, error)
CreateTemplate(ctx context.Context, template *Template) error
GetTemplate(ctx context.Context, name string) (Template, error)
UpdateTemplate(ctx context.Context, template *Template) error
DeleteTemplate(ctx context.Context, name string) error
ListTemplates(opts *ListTemplateOptions) *TemplatesIterator
AddTemplateVersion(ctx context.Context, templateName string, version *TemplateVersion) error
GetTemplateVersion(ctx context.Context, templateName, tag string) (TemplateVersion, error)
UpdateTemplateVersion(ctx context.Context, templateName string, version *TemplateVersion) error
DeleteTemplateVersion(ctx context.Context, templateName, tag string) error
ListTemplateVersions(templateName string, opts *ListOptions) *TemplateVersionsIterator
}
// MailgunImpl bundles data needed by a large number of methods in order to interact with the Mailgun API.
// Colloquially, we refer to instances of this structure as "clients."
type MailgunImpl struct {
apiBase string
domain string
apiKey string
client *http.Client
baseURL string
}
// NewMailGun creates a new client instance.
func NewMailgun(domain, apiKey string) *MailgunImpl {
return &MailgunImpl{
apiBase: APIBase,
domain: domain,
apiKey: apiKey,
client: http.DefaultClient,
}
}
// NewMailgunFromEnv returns a new Mailgun client using the environment variables
// MG_API_KEY, MG_DOMAIN, and MG_URL
func NewMailgunFromEnv() (*MailgunImpl, error) {
apiKey := os.Getenv("MG_API_KEY")
if apiKey == "" {
return nil, errors.New("required environment variable MG_API_KEY not defined")
}
domain := os.Getenv("MG_DOMAIN")
if domain == "" {
return nil, errors.New("required environment variable MG_DOMAIN not defined")
}
mg := NewMailgun(domain, apiKey)
url := os.Getenv("MG_URL")
if url != "" {
mg.SetAPIBase(url)
}
return mg, nil
}
// APIBase returns the API Base URL configured for this client.
func (mg *MailgunImpl) APIBase() string {
return mg.apiBase
}
// Domain returns the domain configured for this client.
func (mg *MailgunImpl) Domain() string {
return mg.domain
}
// ApiKey returns the API key configured for this client.
func (mg *MailgunImpl) APIKey() string {
return mg.apiKey
}
// Client returns the HTTP client configured for this client.
func (mg *MailgunImpl) Client() *http.Client {
return mg.client
}
// SetClient updates the HTTP client for this client.
func (mg *MailgunImpl) SetClient(c *http.Client) {
mg.client = c
}
// SetAPIBase updates the API Base URL for this client.
// // For EU Customers
// mg.SetAPIBase(mailgun.APIBaseEU)
//
// // For US Customers
// mg.SetAPIBase(mailgun.APIBaseUS)
//
// // Set a custom base API
// mg.SetAPIBase("https://localhost/v3")
func (mg *MailgunImpl) SetAPIBase(address string) {
mg.apiBase = address
}
// generateApiUrl renders a URL for an API endpoint using the domain and endpoint name.
func generateApiUrl(m Mailgun, endpoint string) string {
return fmt.Sprintf("%s/%s/%s", m.APIBase(), m.Domain(), endpoint)
}
// generateApiUrlWithDomain renders a URL for an API endpoint using a separate domain and endpoint name.
func generateApiUrlWithDomain(m Mailgun, endpoint, domain string) string {
return fmt.Sprintf("%s/%s/%s", m.APIBase(), domain, endpoint)
}
// generateMemberApiUrl renders a URL relevant for specifying mailing list members.
// The address parameter refers to the mailing list in question.
func generateMemberApiUrl(m Mailgun, endpoint, address string) string {
return fmt.Sprintf("%s/%s/%s/members", m.APIBase(), endpoint, address)
}
// generateApiUrlWithTarget works as generateApiUrl,
// but consumes an additional resource parameter called 'target'.
func generateApiUrlWithTarget(m Mailgun, endpoint, target string) string {
tail := ""
if target != "" {
tail = fmt.Sprintf("/%s", target)
}
return fmt.Sprintf("%s%s", generateApiUrl(m, endpoint), tail)
}
// generateDomainApiUrl renders a URL as generateApiUrl, but
// addresses a family of functions which have a non-standard URL structure.
// Most URLs consume a domain in the 2nd position, but some endpoints
// require the word "domains" to be there instead.
func generateDomainApiUrl(m Mailgun, endpoint string) string {
return fmt.Sprintf("%s/domains/%s/%s", m.APIBase(), m.Domain(), endpoint)
}
// generateCredentialsUrl renders a URL as generateDomainApiUrl,
// but focuses on the SMTP credentials family of API functions.
func generateCredentialsUrl(m Mailgun, login string) string {
tail := ""
if login != "" {
tail = fmt.Sprintf("/%s", login)
}
return generateDomainApiUrl(m, fmt.Sprintf("credentials%s", tail))
// return fmt.Sprintf("%s/domains/%s/credentials%s", apiBase, m.Domain(), tail)
}
// generateStoredMessageUrl generates the URL needed to acquire a copy of a stored message.
func generateStoredMessageUrl(m Mailgun, endpoint, id string) string {
return generateDomainApiUrl(m, fmt.Sprintf("%s/%s", endpoint, id))
// return fmt.Sprintf("%s/domains/%s/%s/%s", apiBase, m.Domain(), endpoint, id)
}
// generatePublicApiUrl works as generateApiUrl, except that generatePublicApiUrl has no need for the domain.
func generatePublicApiUrl(m Mailgun, endpoint string) string {
return fmt.Sprintf("%s/%s", m.APIBase(), endpoint)
}
// generateParameterizedUrl works as generateApiUrl, but supports query parameters.
func generateParameterizedUrl(m Mailgun, endpoint string, payload payload) (string, error) {
paramBuffer, err := payload.getPayloadBuffer()
if err != nil {
return "", err
}
params := string(paramBuffer.Bytes())
return fmt.Sprintf("%s?%s", generateApiUrl(m, eventsEndpoint), params), nil
}
// parseMailgunTime translates a timestamp as returned by Mailgun into a Go standard timestamp.
func parseMailgunTime(ts string) (t time.Time, err error) {
t, err = time.Parse("Mon, 2 Jan 2006 15:04:05 MST", ts)
return
}
// formatMailgunTime translates a timestamp into a human-readable form.
func formatMailgunTime(t time.Time) string {
return t.Format("Mon, 2 Jan 2006 15:04:05 -0700")
}

View File

@@ -0,0 +1,249 @@
package mailgun
import (
"context"
"strconv"
)
// A mailing list may have one of three membership modes.
const (
// ReadOnly specifies that nobody, including Members, may send messages to
// the mailing list. Messages distributed on such lists come from list
// administrator accounts only.
AccessLevelReadOnly = "readonly"
// Members specifies that only those who subscribe to the mailing list may
// send messages.
AccessLevelMembers = "members"
// Everyone specifies that anyone and everyone may both read and submit
// messages to the mailing list, including non-subscribers.
AccessLevelEveryone = "everyone"
)
// Specify the access of a mailing list member
type AccessLevel string
// A List structure provides information for a mailing list.
//
// AccessLevel may be one of ReadOnly, Members, or Everyone.
type MailingList struct {
Address string `json:"address,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AccessLevel AccessLevel `json:"access_level,omitempty"`
CreatedAt RFC2822Time `json:"created_at,omitempty"`
MembersCount int `json:"members_count,omitempty"`
}
type listsResponse struct {
Items []MailingList `json:"items"`
Paging Paging `json:"paging"`
}
type mailingListResponse struct {
MailingList MailingList `json:"member"`
}
type ListsIterator struct {
listsResponse
mg Mailgun
err error
}
// ListMailingLists returns the specified set of mailing lists administered by your account.
func (mg *MailgunImpl) ListMailingLists(opts *ListOptions) *ListsIterator {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/pages")
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 &ListsIterator{
mg: mg,
listsResponse: listsResponse{Paging: Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (li *ListsIterator) Err() error {
return li.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 (li *ListsIterator) Next(ctx context.Context, items *[]MailingList) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Next)
if li.err != nil {
return false
}
cpy := make([]MailingList, len(li.Items))
copy(cpy, li.Items)
*items = cpy
if len(li.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 (li *ListsIterator) First(ctx context.Context, items *[]MailingList) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.First)
if li.err != nil {
return false
}
cpy := make([]MailingList, len(li.Items))
copy(cpy, li.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 (li *ListsIterator) Last(ctx context.Context, items *[]MailingList) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Last)
if li.err != nil {
return false
}
cpy := make([]MailingList, len(li.Items))
copy(cpy, li.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 (li *ListsIterator) Previous(ctx context.Context, items *[]MailingList) bool {
if li.err != nil {
return false
}
if li.Paging.Previous == "" {
return false
}
li.err = li.fetch(ctx, li.Paging.Previous)
if li.err != nil {
return false
}
cpy := make([]MailingList, len(li.Items))
copy(cpy, li.Items)
*items = cpy
if len(li.Items) == 0 {
return false
}
return true
}
func (li *ListsIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(li.mg.Client())
r.setBasicAuth(basicAuthUser, li.mg.APIKey())
return getResponseFromJSON(ctx, r, &li.listsResponse)
}
// CreateMailingList creates a new mailing list under your Mailgun account.
// You need specify only the Address and Name members of the prototype;
// Description, and AccessLevel are optional.
// If unspecified, Description remains blank,
// while AccessLevel defaults to Everyone.
func (mg *MailgunImpl) CreateMailingList(ctx context.Context, prototype MailingList) (MailingList, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if prototype.Address != "" {
p.addValue("address", prototype.Address)
}
if prototype.Name != "" {
p.addValue("name", prototype.Name)
}
if prototype.Description != "" {
p.addValue("description", prototype.Description)
}
if prototype.AccessLevel != "" {
p.addValue("access_level", string(prototype.AccessLevel))
}
response, err := makePostRequest(ctx, r, p)
if err != nil {
return MailingList{}, err
}
var l MailingList
err = response.parseFromJSON(&l)
return l, err
}
// DeleteMailingList removes all current members of the list, then removes the list itself.
// Attempts to send e-mail to the list will fail subsequent to this call.
func (mg *MailgunImpl) DeleteMailingList(ctx context.Context, addr string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetMailingList allows your application to recover the complete List structure
// representing a mailing list, so long as you have its e-mail address.
func (mg *MailgunImpl) GetMailingList(ctx context.Context, addr string) (MailingList, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
response, err := makeGetRequest(ctx, r)
if err != nil {
return MailingList{}, err
}
var resp mailingListResponse
err = response.parseFromJSON(&resp)
return resp.MailingList, err
}
// UpdateMailingList allows you to change various attributes of a list.
// Address, Name, Description, and AccessLevel are all optional;
// only those fields which are set in the prototype will change.
//
// Be careful! If changing the address of a mailing list,
// e-mail sent to the old address will not succeed.
// Make sure you account for the change accordingly.
func (mg *MailgunImpl) UpdateMailingList(ctx context.Context, addr string, prototype MailingList) (MailingList, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if prototype.Address != "" {
p.addValue("address", prototype.Address)
}
if prototype.Name != "" {
p.addValue("name", prototype.Name)
}
if prototype.Description != "" {
p.addValue("description", prototype.Description)
}
if prototype.AccessLevel != "" {
p.addValue("access_level", string(prototype.AccessLevel))
}
var l MailingList
response, err := makePutRequest(ctx, r, p)
if err != nil {
return l, err
}
err = response.parseFromJSON(&l)
return l, err
}

264
vendor/github.com/mailgun/mailgun-go/v4/members.go generated vendored Normal file
View File

@@ -0,0 +1,264 @@
package mailgun
import (
"context"
"encoding/json"
"strconv"
)
// yes and no are variables which provide us the ability to take their addresses.
// Subscribed and Unsubscribed are pointers to these booleans.
//
// We use a pointer to boolean as a kind of trinary data type:
// if nil, the relevant data type remains unspecified.
// Otherwise, its value is either true or false.
var (
yes bool = true
no bool = false
)
// Mailing list members have an attribute that determines if they've subscribed to the mailing list or not.
// This attribute may be used to filter the results returned by GetSubscribers().
// All, Subscribed, and Unsubscribed provides a convenient and readable syntax for specifying the scope of the search.
var (
All *bool = nil
Subscribed *bool = &yes
Unsubscribed *bool = &no
)
// A Member structure represents a member of the mailing list.
// The Vars field can represent any JSON-encodable data.
type Member struct {
Address string `json:"address,omitempty"`
Name string `json:"name,omitempty"`
Subscribed *bool `json:"subscribed,omitempty"`
Vars map[string]interface{} `json:"vars,omitempty"`
}
type memberListResponse struct {
Lists []Member `json:"items"`
Paging Paging `json:"paging"`
}
type memberResponse struct {
Member Member `json:"member"`
}
type MemberListIterator struct {
memberListResponse
mg Mailgun
err error
}
// Used by List methods to specify what list parameters to send to the mailgun API
type ListOptions struct {
Limit int
}
func (mg *MailgunImpl) ListMembers(address string, opts *ListOptions) *MemberListIterator {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, address) + "/pages")
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 &MemberListIterator{
mg: mg,
memberListResponse: memberListResponse{Paging: Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (li *MemberListIterator) Err() error {
return li.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 (li *MemberListIterator) Next(ctx context.Context, items *[]Member) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Next)
if li.err != nil {
return false
}
*items = li.Lists
if len(li.Lists) == 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 (li *MemberListIterator) First(ctx context.Context, items *[]Member) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.First)
if li.err != nil {
return false
}
*items = li.Lists
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 (li *MemberListIterator) Last(ctx context.Context, items *[]Member) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Last)
if li.err != nil {
return false
}
*items = li.Lists
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 (li *MemberListIterator) Previous(ctx context.Context, items *[]Member) bool {
if li.err != nil {
return false
}
if li.Paging.Previous == "" {
return false
}
li.err = li.fetch(ctx, li.Paging.Previous)
if li.err != nil {
return false
}
*items = li.Lists
if len(li.Lists) == 0 {
return false
}
return true
}
func (li *MemberListIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(li.mg.Client())
r.setBasicAuth(basicAuthUser, li.mg.APIKey())
return getResponseFromJSON(ctx, r, &li.memberListResponse)
}
// GetMember returns a complete Member structure for a member of a mailing list,
// given only their subscription e-mail address.
func (mg *MailgunImpl) GetMember(ctx context.Context, s, l string) (Member, error) {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, l) + "/" + s)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
response, err := makeGetRequest(ctx, r)
if err != nil {
return Member{}, err
}
var resp memberResponse
err = response.parseFromJSON(&resp)
return resp.Member, err
}
// CreateMember registers a new member of the indicated mailing list.
// If merge is set to true, then the registration may update an existing Member's settings.
// Otherwise, an error will occur if you attempt to add a member with a duplicate e-mail address.
func (mg *MailgunImpl) CreateMember(ctx context.Context, merge bool, addr string, prototype Member) error {
vs, err := json.Marshal(prototype.Vars)
if err != nil {
return err
}
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newFormDataPayload()
p.addValue("upsert", yesNo(merge))
p.addValue("address", prototype.Address)
p.addValue("name", prototype.Name)
p.addValue("vars", string(vs))
if prototype.Subscribed != nil {
p.addValue("subscribed", yesNo(*prototype.Subscribed))
}
_, err = makePostRequest(ctx, r, p)
return err
}
// UpdateMember lets you change certain details about the indicated mailing list member.
// Address, Name, Vars, and Subscribed fields may be changed.
func (mg *MailgunImpl) UpdateMember(ctx context.Context, s, l string, prototype Member) (Member, error) {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, l) + "/" + s)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newFormDataPayload()
if prototype.Address != "" {
p.addValue("address", prototype.Address)
}
if prototype.Name != "" {
p.addValue("name", prototype.Name)
}
if prototype.Vars != nil {
vs, err := json.Marshal(prototype.Vars)
if err != nil {
return Member{}, err
}
p.addValue("vars", string(vs))
}
if prototype.Subscribed != nil {
p.addValue("subscribed", yesNo(*prototype.Subscribed))
}
response, err := makePutRequest(ctx, r, p)
if err != nil {
return Member{}, err
}
var envelope struct {
Member Member `json:"member"`
}
err = response.parseFromJSON(&envelope)
return envelope.Member, err
}
// DeleteMember removes the member from the list.
func (mg *MailgunImpl) DeleteMember(ctx context.Context, member, addr string) error {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr) + "/" + member)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// CreateMemberList registers multiple Members and non-Member members to a single mailing list
// in a single round-trip.
// u indicates if the existing members should be updated or duplicates should be updated.
// Use All to elect not to provide a default.
// The newMembers list can take one of two JSON-encodable forms: an slice of strings, or
// a slice of Member structures.
// If a simple slice of strings is passed, each string refers to the member's e-mail address.
// Otherwise, each Member needs to have at least the Address field filled out.
// Other fields are optional, but may be set according to your needs.
func (mg *MailgunImpl) CreateMemberList(ctx context.Context, u *bool, addr string, newMembers []interface{}) error {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr) + ".json")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newFormDataPayload()
if u != nil {
p.addValue("upsert", yesNo(*u))
}
bs, err := json.Marshal(newMembers)
if err != nil {
return err
}
p.addValue("members", string(bs))
_, err = makePostRequest(ctx, r, p)
return err
}

839
vendor/github.com/mailgun/mailgun-go/v4/messages.go generated vendored Normal file
View File

@@ -0,0 +1,839 @@
package mailgun
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"time"
)
// MaxNumberOfRecipients represents the largest batch of recipients that Mailgun can support in a single API call.
// This figure includes To:, Cc:, Bcc:, etc. recipients.
const MaxNumberOfRecipients = 1000
// MaxNumberOfTags represents the maximum number of tags that can be added for a message
const MaxNumberOfTags = 3
// Message structures contain both the message text and the envelop for an e-mail message.
type Message struct {
to []string
tags []string
campaigns []string
dkim bool
deliveryTime time.Time
attachments []string
readerAttachments []ReaderAttachment
inlines []string
readerInlines []ReaderAttachment
bufferAttachments []BufferAttachment
nativeSend bool
testMode bool
tracking bool
trackingClicks bool
trackingOpens bool
headers map[string]string
variables map[string]string
templateVariables map[string]interface{}
recipientVariables map[string]map[string]interface{}
domain string
templateVersionTag string
templateRenderText bool
dkimSet bool
trackingSet bool
trackingClicksSet bool
trackingOpensSet bool
requireTLS bool
skipVerification bool
specific features
mg Mailgun
}
type ReaderAttachment struct {
Filename string
ReadCloser io.ReadCloser
}
type BufferAttachment struct {
Filename string
Buffer []byte
}
// StoredMessage structures contain the (parsed) message content for an email
// sent to a Mailgun account.
//
// The MessageHeaders field is special, in that it's formatted as a slice of pairs.
// Each pair consists of a name [0] and value [1]. Array notation is used instead of a map
// because that's how it's sent over the wire, and it's how encoding/json expects this field
// to be.
type StoredMessage struct {
Recipients string `json:"recipients"`
Sender string `json:"sender"`
From string `json:"from"`
Subject string `json:"subject"`
BodyPlain string `json:"body-plain"`
StrippedText string `json:"stripped-text"`
StrippedSignature string `json:"stripped-signature"`
BodyHtml string `json:"body-html"`
StrippedHtml string `json:"stripped-html"`
Attachments []StoredAttachment `json:"attachments"`
MessageUrl string `json:"message-url"`
ContentIDMap map[string]struct {
Url string `json:"url"`
ContentType string `json:"content-type"`
Name string `json:"name"`
Size int64 `json:"size"`
} `json:"content-id-map"`
MessageHeaders [][]string `json:"message-headers"`
}
// StoredAttachment structures contain information on an attachment associated with a stored message.
type StoredAttachment struct {
Size int `json:"size"`
Url string `json:"url"`
Name string `json:"name"`
ContentType string `json:"content-type"`
}
type StoredMessageRaw struct {
Recipients string `json:"recipients"`
Sender string `json:"sender"`
From string `json:"from"`
Subject string `json:"subject"`
BodyMime string `json:"body-mime"`
}
// plainMessage contains fields relevant to plain API-synthesized messages.
// You're expected to use various setters to set most of these attributes,
// although from, subject, and text are set when the message is created with
// NewMessage.
type plainMessage struct {
from string
cc []string
bcc []string
subject string
text string
html string
ampHtml string
template string
}
// mimeMessage contains fields relevant to pre-packaged MIME messages.
type mimeMessage struct {
body io.ReadCloser
}
type sendMessageResponse struct {
Message string `json:"message"`
Id string `json:"id"`
}
// features abstracts the common characteristics between regular and MIME messages.
// addCC, addBCC, recipientCount, setHtml and setAMPHtml are invoked via the package-global AddCC, AddBCC,
// RecipientCount, SetHtml and SetAMPHtml calls, as these functions are ignored for MIME messages.
// Send() invokes addValues to add message-type-specific MIME headers for the API call
// to Mailgun. isValid yeilds true if and only if the message is valid enough for sending
// through the API. Finally, endpoint() tells Send() which endpoint to use to submit the API call.
type features interface {
addCC(string)
addBCC(string)
setHtml(string)
setAMPHtml(string)
addValues(*formDataPayload)
isValid() bool
endpoint() string
recipientCount() int
setTemplate(string)
}
// NewMessage returns a new e-mail message with the simplest envelop needed to send.
//
// Unlike the global function,
// this method supports arbitrary-sized recipient lists by
// automatically sending mail in batches of up to MaxNumberOfRecipients.
//
// To support batch sending, you don't want to provide a fixed To: header at this point.
// 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("me@example.com", "Help save our planet", "Hello world!")
//
// Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method
// before sending, though.
func (mg *MailgunImpl) NewMessage(from, subject, text string, to ...string) *Message {
return &Message{
specific: &plainMessage{
from: from,
subject: subject,
text: text,
},
to: to,
mg: mg,
}
}
// NewMIMEMessage creates a new MIME message. These messages are largely canned;
// you do not need to invoke setters to set message-related headers.
// However, you do still need to call setters for Mailgun-specific settings.
//
// Unlike the global function,
// this method supports arbitrary-sized recipient lists by
// automatically sending mail in batches of up to MaxNumberOfRecipients.
//
// To support batch sending, you don't want to provide a fixed To: header at this point.
// 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("me@example.com", "Help save our planet", "Hello world!")
//
// Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method
// before sending, though.
func (mg *MailgunImpl) NewMIMEMessage(body io.ReadCloser, to ...string) *Message {
return &Message{
specific: &mimeMessage{
body: body,
},
to: to,
mg: mg,
}
}
// AddReaderAttachment arranges to send a file along with the e-mail message.
// File contents are read from a io.ReadCloser.
// The filename parameter is the resulting filename of the attachment.
// The readCloser parameter is the io.ReadCloser which reads the actual bytes to be used
// as the contents of the attached file.
func (m *Message) AddReaderAttachment(filename string, readCloser io.ReadCloser) {
ra := ReaderAttachment{Filename: filename, ReadCloser: readCloser}
m.readerAttachments = append(m.readerAttachments, ra)
}
// AddBufferAttachment arranges to send a file along with the e-mail message.
// File contents are read from the []byte array provided
// The filename parameter is the resulting filename of the attachment.
// The buffer parameter is the []byte array which contains the actual bytes to be used
// as the contents of the attached file.
func (m *Message) AddBufferAttachment(filename string, buffer []byte) {
ba := BufferAttachment{Filename: filename, Buffer: buffer}
m.bufferAttachments = append(m.bufferAttachments, ba)
}
// AddAttachment arranges to send a file from the filesystem along with the e-mail message.
// The attachment parameter is a filename, which must refer to a file which actually resides
// in the local filesystem.
func (m *Message) AddAttachment(attachment string) {
m.attachments = append(m.attachments, attachment)
}
// AddReaderInline arranges to send a file along with the e-mail message.
// File contents are read from a io.ReadCloser.
// The filename parameter is the resulting filename of the attachment.
// The readCloser parameter is the io.ReadCloser which reads the actual bytes to be used
// as the contents of the attached file.
func (m *Message) AddReaderInline(filename string, readCloser io.ReadCloser) {
ra := ReaderAttachment{Filename: filename, ReadCloser: readCloser}
m.readerInlines = append(m.readerInlines, ra)
}
// AddInline arranges to send a file along with the e-mail message, but does so
// in a way that its data remains "inline" with the rest of the message. This
// can be used to send image or font data along with an HTML-encoded message body.
// The attachment parameter is a filename, which must refer to a file which actually resides
// in the local filesystem.
func (m *Message) AddInline(inline string) {
m.inlines = append(m.inlines, inline)
}
// AddRecipient appends a receiver to the To: header of a message.
// It will return an error if the limit of recipients have been exceeded for this message
func (m *Message) AddRecipient(recipient string) error {
return m.AddRecipientAndVariables(recipient, nil)
}
// AddRecipientAndVariables appends a receiver to the To: header of a message,
// and as well attaches a set of variables relevant for this recipient.
// It will return an error if the limit of recipients have been exceeded for this message
func (m *Message) AddRecipientAndVariables(r string, vars map[string]interface{}) error {
if m.RecipientCount() >= MaxNumberOfRecipients {
return fmt.Errorf("recipient limit exceeded (max %d)", MaxNumberOfRecipients)
}
m.to = append(m.to, r)
if vars != nil {
if m.recipientVariables == nil {
m.recipientVariables = make(map[string]map[string]interface{})
}
m.recipientVariables[r] = vars
}
return nil
}
// RecipientCount returns the total number of recipients for the message.
// This includes To:, Cc:, and Bcc: fields.
//
// NOTE: At present, this method is reliable only for non-MIME messages, as the
// Bcc: and Cc: fields are easily accessible.
// For MIME messages, only the To: field is considered.
// A fix for this issue is planned for a future release.
// For now, MIME messages are always assumed to have 10 recipients between Cc: and Bcc: fields.
// If your MIME messages have more than 10 non-To: field recipients,
// you may find that some recipients will not receive your e-mail.
// It's perfectly OK, of course, for a MIME message to not have any Cc: or Bcc: recipients.
func (m *Message) RecipientCount() int {
return len(m.to) + m.specific.recipientCount()
}
func (pm *plainMessage) recipientCount() int {
return len(pm.bcc) + len(pm.cc)
}
func (mm *mimeMessage) recipientCount() int {
return 10
}
func (m *Message) send(ctx context.Context) (string, string, error) {
return m.mg.Send(ctx, m)
}
// SetReplyTo sets the receiver who should receive replies
func (m *Message) SetReplyTo(recipient string) {
m.AddHeader("Reply-To", recipient)
}
// AddCC appends a receiver to the carbon-copy header of a message.
func (m *Message) AddCC(recipient string) {
m.specific.addCC(recipient)
}
func (pm *plainMessage) addCC(r string) {
pm.cc = append(pm.cc, r)
}
func (mm *mimeMessage) addCC(_ string) {}
// AddBCC appends a receiver to the blind-carbon-copy header of a message.
func (m *Message) AddBCC(recipient string) {
m.specific.addBCC(recipient)
}
func (pm *plainMessage) addBCC(r string) {
pm.bcc = append(pm.bcc, r)
}
func (mm *mimeMessage) addBCC(_ string) {}
// SetHtml is a helper. If you're sending a message that isn't already MIME encoded, SetHtml() will arrange to bundle
// an HTML representation of your message in addition to your plain-text body.
func (m *Message) SetHtml(html string) {
m.specific.setHtml(html)
}
func (pm *plainMessage) setHtml(h string) {
pm.html = h
}
func (mm *mimeMessage) setHtml(_ string) {}
// SetAMP is a helper. If you're sending a message that isn't already MIME encoded, SetAMP() will arrange to bundle
// an AMP-For-Email representation of your message in addition to your html & plain-text content.
func (m *Message) SetAMPHtml(html string) {
m.specific.setAMPHtml(html)
}
func (pm *plainMessage) setAMPHtml(h string) {
pm.ampHtml = h
}
func (mm *mimeMessage) setAMPHtml(_ string) {}
// AddTag attaches tags to the message. Tags are useful for metrics gathering and event tracking purposes.
// Refer to the Mailgun documentation for further details.
func (m *Message) AddTag(tag ...string) error {
if len(m.tags) >= MaxNumberOfTags {
return fmt.Errorf("cannot add any new tags. Message tag limit (%d) reached", MaxNumberOfTags)
}
m.tags = append(m.tags, tag...)
return nil
}
// SetTemplate sets the name of a template stored via the template API.
// See https://documentation.mailgun.com/en/latest/user_manual.html#templating
func (m *Message) SetTemplate(t string) {
m.specific.setTemplate(t)
}
func (pm *plainMessage) setTemplate(t string) {
pm.template = t
}
func (mm *mimeMessage) setTemplate(t string) {}
// AddCampaign is no longer supported and is deprecated for new software.
func (m *Message) AddCampaign(campaign string) {
m.campaigns = append(m.campaigns, campaign)
}
// SetDKIM arranges to send the o:dkim header with the message, and sets its value accordingly.
// Refer to the Mailgun documentation for more information.
func (m *Message) SetDKIM(dkim bool) {
m.dkim = dkim
m.dkimSet = true
}
// EnableNativeSend allows the return path to match the address in the Message.Headers.From:
// field when sending from Mailgun rather than the usual bounce+ address in the return path.
func (m *Message) EnableNativeSend() {
m.nativeSend = true
}
// EnableTestMode allows submittal of a message, such that it will be discarded by Mailgun.
// This facilitates testing client-side software without actually consuming e-mail resources.
func (m *Message) EnableTestMode() {
m.testMode = true
}
// SetDeliveryTime schedules the message for transmission at the indicated time.
// Pass nil to remove any installed schedule.
// Refer to the Mailgun documentation for more information.
func (m *Message) SetDeliveryTime(dt time.Time) {
m.deliveryTime = dt
}
// 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.
// Note: simply calling this method ensures that the o:tracking header is passed in with the message.
// Its yes/no setting is determined by the call's parameter.
// Note that this header is not passed on to the final recipient(s).
// Refer to the Mailgun documentation for more information.
func (m *Message) SetTracking(tracking bool) {
m.tracking = tracking
m.trackingSet = true
}
// SetTrackingClicks information is found in the Mailgun documentation.
func (m *Message) SetTrackingClicks(trackingClicks bool) {
m.trackingClicks = trackingClicks
m.trackingClicksSet = true
}
// SetRequireTLS information is found in the Mailgun documentation.
func (m *Message) SetRequireTLS(b bool) {
m.requireTLS = b
}
// SetSkipVerification information is found in the Mailgun documentation.
func (m *Message) SetSkipVerification(b bool) {
m.skipVerification = b
}
//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.
func (m *Message) SetTemplateVersion(tag string) {
m.templateVersionTag = tag
}
//SetTemplateRenderText information is found in the Mailgun documentation.
func (m *Message) SetTemplateRenderText(render bool) {
m.templateRenderText = render
}
// AddHeader allows you to send custom MIME headers with the message.
func (m *Message) AddHeader(header, value string) {
if m.headers == nil {
m.headers = make(map[string]string)
}
m.headers[header] = value
}
// AddVariable lets you associate a set of variables with messages you send,
// which Mailgun can use to, in essence, complete form-mail.
// Refer to the Mailgun documentation for more information.
func (m *Message) AddVariable(variable string, value interface{}) error {
if m.variables == nil {
m.variables = make(map[string]string)
}
j, err := json.Marshal(value)
if err != nil {
return err
}
encoded := string(j)
v, err := strconv.Unquote(encoded)
if err != nil {
v = encoded
}
m.variables[variable] = v
return nil
}
// AddTemplateVariable adds a template variable to the map of template variables, replacing the variable if it is already there.
// This is used for server-side message templates and can nest arbitrary values. At send time, the resulting map will be converted into
// a JSON string and sent as a header in the X-Mailgun-Variables header.
func (m *Message) AddTemplateVariable(variable string, value interface{}) error {
if m.templateVariables == nil {
m.templateVariables = make(map[string]interface{})
}
m.templateVariables[variable] = value
return nil
}
// AddDomain allows you to use a separate domain for the type of messages you are sending.
func (m *Message) AddDomain(domain string) {
m.domain = domain
}
// GetHeaders retrieves the http headers associated with this message
func (m *Message) GetHeaders() map[string]string {
return m.headers
}
// ErrInvalidMessage is returned by `Send()` when the `mailgun.Message` struct is incomplete
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, and a message ID. The status and message ID are set only
// if no error occurred.
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()")
return
}
if mg.apiKey == "" {
err = errors.New("you must provide a valid api-key before calling Send()")
return
}
if !isValid(message) {
err = ErrInvalidMessage
return
}
payload := newFormDataPayload()
message.specific.addValues(payload)
for _, to := range message.to {
payload.addValue("to", to)
}
for _, tag := range message.tags {
payload.addValue("o:tag", tag)
}
for _, campaign := range message.campaigns {
payload.addValue("o:campaign", campaign)
}
if message.dkimSet {
payload.addValue("o:dkim", yesNo(message.dkim))
}
if !message.deliveryTime.IsZero() {
payload.addValue("o:deliverytime", formatMailgunTime(message.deliveryTime))
}
if message.nativeSend {
payload.addValue("o:native-send", "yes")
}
if message.testMode {
payload.addValue("o:testmode", "yes")
}
if message.trackingSet {
payload.addValue("o:tracking", yesNo(message.tracking))
}
if message.trackingClicksSet {
payload.addValue("o:tracking-clicks", yesNo(message.trackingClicks))
}
if message.trackingOpensSet {
payload.addValue("o:tracking-opens", yesNo(message.trackingOpens))
}
if message.requireTLS {
payload.addValue("o:require-tls", trueFalse(message.requireTLS))
}
if message.skipVerification {
payload.addValue("o:skip-verification", trueFalse(message.skipVerification))
}
if message.headers != nil {
for header, value := range message.headers {
payload.addValue("h:"+header, value)
}
}
if message.variables != nil {
for variable, value := range message.variables {
payload.addValue("v:"+variable, value)
}
}
if message.templateVariables != nil {
variableString, err := json.Marshal(message.templateVariables)
if err == nil {
// the map was marshalled as json so add it
payload.addValue("h:X-Mailgun-Variables", string(variableString))
}
}
if message.recipientVariables != nil {
j, err := json.Marshal(message.recipientVariables)
if err != nil {
return "", "", err
}
payload.addValue("recipient-variables", string(j))
}
if message.attachments != nil {
for _, attachment := range message.attachments {
payload.addFile("attachment", attachment)
}
}
if message.readerAttachments != nil {
for _, readerAttachment := range message.readerAttachments {
payload.addReadCloser("attachment", readerAttachment.Filename, readerAttachment.ReadCloser)
}
}
if message.bufferAttachments != nil {
for _, bufferAttachment := range message.bufferAttachments {
payload.addBuffer("attachment", bufferAttachment.Filename, bufferAttachment.Buffer)
}
}
if message.inlines != nil {
for _, inline := range message.inlines {
payload.addFile("inline", inline)
}
}
if message.readerInlines != nil {
for _, readerAttachment := range message.readerInlines {
payload.addReadCloser("inline", readerAttachment.Filename, readerAttachment.ReadCloser)
}
}
if message.domain == "" {
message.domain = mg.Domain()
}
if message.templateVersionTag != "" {
payload.addValue("t:version", message.templateVersionTag)
}
if message.templateRenderText {
payload.addValue("t:text", yesNo(message.templateRenderText))
}
r := newHTTPRequest(generateApiUrlWithDomain(mg, message.specific.endpoint(), message.domain))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var response sendMessageResponse
err = postResponseFromJSON(ctx, r, payload, &response)
if err == nil {
mes = response.Message
id = response.Id
}
return
}
func (pm *plainMessage) addValues(p *formDataPayload) {
p.addValue("from", pm.from)
p.addValue("subject", pm.subject)
p.addValue("text", pm.text)
for _, cc := range pm.cc {
p.addValue("cc", cc)
}
for _, bcc := range pm.bcc {
p.addValue("bcc", bcc)
}
if pm.html != "" {
p.addValue("html", pm.html)
}
if pm.template != "" {
p.addValue("template", pm.template)
}
if pm.ampHtml != "" {
p.addValue("amp-html", pm.ampHtml)
}
}
func (mm *mimeMessage) addValues(p *formDataPayload) {
p.addReadCloser("message", "message.mime", mm.body)
}
func (pm *plainMessage) endpoint() string {
return messagesEndpoint
}
func (mm *mimeMessage) endpoint() string {
return mimeMessagesEndpoint
}
// yesNo translates a true/false boolean value into a yes/no setting suitable for the Mailgun API.
func yesNo(b bool) string {
if b {
return "yes"
}
return "no"
}
func trueFalse(b bool) string {
if b {
return "true"
}
return "false"
}
// isValid returns true if, and only if,
// a Message instance is sufficiently initialized to send via the Mailgun interface.
func isValid(m *Message) bool {
if m == nil {
return false
}
if !m.specific.isValid() {
return false
}
if m.RecipientCount() == 0 {
return false
}
if !validateStringList(m.tags, false) {
return false
}
if !validateStringList(m.campaigns, false) || len(m.campaigns) > 3 {
return false
}
return true
}
func (pm *plainMessage) isValid() bool {
if pm.from == "" {
return false
}
if !validateStringList(pm.cc, false) {
return false
}
if !validateStringList(pm.bcc, false) {
return false
}
if pm.template != "" {
// pm.text or pm.html not needed if template is supplied
return true
}
if pm.text == "" && pm.html == "" {
return false
}
return true
}
func (mm *mimeMessage) isValid() bool {
return mm.body != nil
}
// validateStringList returns true if, and only if,
// a slice of strings exists AND all of its elements exist,
// OR if the slice doesn't exist AND it's not required to exist.
// The requireOne parameter indicates whether the list is required to exist.
func validateStringList(list []string, requireOne bool) bool {
hasOne := false
if list == nil {
return !requireOne
} else {
for _, a := range list {
if a == "" {
return false
} else {
hasOne = hasOne || true
}
}
}
return hasOne
}
// GetStoredMessage retrieves information about a received e-mail message.
// This provides visibility into, e.g., replies to a message sent to a mailing list.
func (mg *MailgunImpl) GetStoredMessage(ctx context.Context, url string) (StoredMessage, error) {
r := newHTTPRequest(url)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var response StoredMessage
err := getResponseFromJSON(ctx, r, &response)
return response, err
}
// Given a storage id resend the stored message to the specified recipients
func (mg *MailgunImpl) ReSend(ctx context.Context, url string, recipients ...string) (string, string, error) {
r := newHTTPRequest(url)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newFormDataPayload()
if len(recipients) == 0 {
return "", "", errors.New("must provide at least one recipient")
}
for _, to := range recipients {
payload.addValue("to", to)
}
var resp sendMessageResponse
err := postResponseFromJSON(ctx, r, payload, &resp)
if err != nil {
return "", "", err
}
return resp.Message, resp.Id, nil
}
// GetStoredMessageRaw retrieves the raw MIME body of a received e-mail message.
// Compared to GetStoredMessage, it gives access to the unparsed MIME body, and
// thus delegates to the caller the required parsing.
func (mg *MailgunImpl) GetStoredMessageRaw(ctx context.Context, url string) (StoredMessageRaw, error) {
r := newHTTPRequest(url)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addHeader("Accept", "message/rfc2822")
var response StoredMessageRaw
err := getResponseFromJSON(ctx, r, &response)
return response, err
}
// Deprecated: Use GetStoreMessage() instead
func (mg *MailgunImpl) GetStoredMessageForURL(ctx context.Context, url string) (StoredMessage, error) {
return mg.GetStoredMessage(ctx, url)
}
// Deprecated: Use GetStoreMessageRaw() instead
func (mg *MailgunImpl) GetStoredMessageRawForURL(ctx context.Context, url string) (StoredMessageRaw, error) {
return mg.GetStoredMessageRaw(ctx, url)
}
// GetStoredAttachment retrieves the raw MIME body of a received e-mail message attachment.
func (mg *MailgunImpl) GetStoredAttachment(ctx context.Context, url string) ([]byte, error) {
r := newHTTPRequest(url)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addHeader("Accept", "message/rfc2822")
response, err := makeGetRequest(ctx, r)
return response.Data, err
}

198
vendor/github.com/mailgun/mailgun-go/v4/mock.go generated vendored Normal file
View File

@@ -0,0 +1,198 @@
package mailgun
import (
"crypto/rand"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/mail"
"net/url"
"strconv"
"strings"
"github.com/go-chi/chi"
)
// A mailgun api mock suitable for testing
type MockServer struct {
srv *httptest.Server
domainIPS []string
domainList []domainContainer
exportList []Export
mailingList []mailingListContainer
routeList []Route
events []Event
webhooks WebHooksListResponse
}
// Create a new instance of the mailgun API mock server
func NewMockServer() MockServer {
ms := MockServer{}
// Add all our handlers
r := chi.NewRouter()
r.Route("/v3", func(r chi.Router) {
ms.addIPRoutes(r)
ms.addExportRoutes(r)
ms.addDomainRoutes(r)
ms.addMailingListRoutes(r)
ms.addEventRoutes(r)
ms.addMessagesRoutes(r)
ms.addRoutes(r)
ms.addWebhookRoutes(r)
})
ms.addValidationRoutes(r)
// Start the server
ms.srv = httptest.NewServer(r)
return ms
}
// Stop the server
func (ms *MockServer) Stop() {
ms.srv.Close()
}
func (ms *MockServer) URL4() string {
return ms.srv.URL + "/v4"
}
// URL returns the URL used to connect to the mock server
func (ms *MockServer) URL() string {
return ms.srv.URL + "/v3"
}
func toJSON(w http.ResponseWriter, obj interface{}) {
if err := json.NewEncoder(w).Encode(obj); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
}
func stringToBool(v string) bool {
lower := strings.ToLower(v)
if lower == "yes" || lower == "no" {
return lower == "yes"
}
if v == "" {
return false
}
result, err := strconv.ParseBool(v)
if err != nil {
panic(err)
}
return result
}
func stringToInt(v string) int {
if v == "" {
return 0
}
result, err := strconv.ParseInt(v, 10, 64)
if err != nil {
panic(err)
}
return int(result)
}
func stringToMap(v string) map[string]interface{} {
if v == "" {
return nil
}
result := make(map[string]interface{})
err := json.Unmarshal([]byte(v), &result)
if err != nil {
panic(err)
}
return result
}
func parseAddress(v string) string {
if v == "" {
return ""
}
e, err := mail.ParseAddress(v)
if err != nil {
panic(err)
}
return e.Address
}
// Given the page direction, pivot value and limit, calculate the offsets for the slice
func pageOffsets(pivotIdx []string, pivotDir, pivotVal string, limit int) (int, int) {
switch pivotDir {
case "first":
if limit < len(pivotIdx) {
return 0, limit
}
return 0, len(pivotIdx)
case "last":
if limit < len(pivotIdx) {
return len(pivotIdx) - limit, len(pivotIdx)
}
return 0, len(pivotIdx)
case "next":
for i, item := range pivotIdx {
if item == pivotVal {
offset := i + 1 + limit
if offset > len(pivotIdx) {
offset = len(pivotIdx)
}
return i + 1, offset
}
}
return 0, 0
case "prev":
for i, item := range pivotIdx {
if item == pivotVal {
if i == 0 {
return 0, 0
}
offset := i - limit
if offset < 0 {
offset = 0
}
return offset, i
}
}
return 0, 0
}
if limit > len(pivotIdx) {
return 0, len(pivotIdx)
}
return 0, limit
}
func getPageURL(r *http.Request, params url.Values) string {
if r.FormValue("limit") != "" {
params.Add("limit", r.FormValue("limit"))
}
return "http://" + r.Host + r.URL.EscapedPath() + "?" + params.Encode()
}
// randomString generates a string of given length, but random content.
// All content will be within the ASCII graphic character set.
// (Implementation from Even Shaw's contribution on
// http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go).
func randomString(n int, prefix string) string {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, n)
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
return prefix + string(bytes)
}
func randomEmail(prefix, domain string) string {
return strings.ToLower(fmt.Sprintf("%s@%s", randomString(20, prefix), domain))
}

283
vendor/github.com/mailgun/mailgun-go/v4/mock_domains.go generated vendored Normal file
View File

@@ -0,0 +1,283 @@
package mailgun
import (
"net/http"
"time"
"github.com/go-chi/chi"
)
type domainContainer struct {
Domain Domain `json:"domain"`
ReceivingDNSRecords []DNSRecord `json:"receiving_dns_records"`
SendingDNSRecords []DNSRecord `json:"sending_dns_records"`
Connection *DomainConnection `json:"connection,omitempty"`
Tracking *DomainTracking `json:"tracking,omitempty"`
TagLimits *TagLimits `json:"limits,omitempty"`
}
func (ms *MockServer) addDomainRoutes(r chi.Router) {
ms.domainList = append(ms.domainList, domainContainer{
Domain: Domain{
CreatedAt: RFC2822Time(time.Now().UTC()),
Name: "mailgun.test",
SMTPLogin: "postmaster@mailgun.test",
SMTPPassword: "4rtqo4p6rrx9",
Wildcard: true,
SpamAction: SpamActionDisabled,
State: "active",
},
Connection: &DomainConnection{
RequireTLS: true,
SkipVerification: true,
},
TagLimits: &TagLimits{
Limit: 50000,
Count: 5000,
},
Tracking: &DomainTracking{
Click: TrackingStatus{Active: true},
Open: TrackingStatus{Active: true},
Unsubscribe: TrackingStatus{
Active: false,
HTMLFooter: "\n<br>\n<p><a href=\"%unsubscribe_url%\">unsubscribe</a></p>\n",
TextFooter: "\n\nTo unsubscribe click: <%unsubscribe_url%>\n\n",
},
},
ReceivingDNSRecords: []DNSRecord{
{
Priority: "10",
RecordType: "MX",
Valid: "valid",
Value: "mxa.mailgun.org",
},
{
Priority: "10",
RecordType: "MX",
Valid: "valid",
Value: "mxb.mailgun.org",
},
},
SendingDNSRecords: []DNSRecord{
{
RecordType: "TXT",
Valid: "valid",
Name: "domain.com",
Value: "v=spf1 include:mailgun.org ~all",
},
{
RecordType: "TXT",
Valid: "valid",
Name: "domain.com",
Value: "k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUA....",
},
{
RecordType: "CNAME",
Valid: "valid",
Name: "email.domain.com",
Value: "mailgun.org",
},
},
})
r.Get("/domains", ms.listDomains)
r.Post("/domains", ms.createDomain)
r.Get("/domains/{domain}", ms.getDomain)
r.Put("/domains/{domain}/verify", ms.getDomain)
r.Delete("/domains/{domain}", ms.deleteDomain)
//r.Get("/domains/{domain}/credentials", ms.getCredentials)
//r.Post("/domains/{domain}/credentials", ms.createCredentials)
//r.Put("/domains/{domain}/credentials/{login}", ms.updateCredentials)
//r.Delete("/domains/{domain}/credentials/{login}", ms.deleteCredentials)
r.Get("/domains/{domain}/connection", ms.getConnection)
r.Put("/domains/{domain}/connection", ms.updateConnection)
r.Get("/domains/{domain}/tracking", ms.getTracking)
r.Put("/domains/{domain}/tracking/click", ms.updateClickTracking)
r.Put("/domains/{domain}/tracking/open", ms.updateOpenTracking)
r.Put("/domains/{domain}/tracking/unsubscribe", ms.updateUnsubTracking)
r.Get("/domains/{domain}/limits/tag", ms.getTagLimits)
}
func (ms *MockServer) listDomains(w http.ResponseWriter, r *http.Request) {
var list []Domain
for _, domain := range ms.domainList {
list = append(list, domain.Domain)
}
skip := stringToInt(r.FormValue("skip"))
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
if skip > len(list) {
skip = len(list)
}
end := limit + skip
if end > len(list) {
end = len(list)
}
// If we are at the end of the list
if skip == end {
toJSON(w, domainsListResponse{
TotalCount: len(list),
Items: []Domain{},
})
return
}
toJSON(w, domainsListResponse{
TotalCount: len(list),
Items: list[skip:end],
})
}
func (ms *MockServer) getDomain(w http.ResponseWriter, r *http.Request) {
for _, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
d.Connection = nil
toJSON(w, d)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) createDomain(w http.ResponseWriter, r *http.Request) {
ms.domainList = append(ms.domainList, domainContainer{
Domain: Domain{
CreatedAt: RFC2822Time(time.Now()),
Name: r.FormValue("name"),
SMTPLogin: r.FormValue("smtp_login"),
SMTPPassword: r.FormValue("smtp_password"),
Wildcard: stringToBool(r.FormValue("wildcard")),
SpamAction: SpamAction(r.FormValue("spam_action")),
State: "active",
},
})
toJSON(w, okResp{Message: "Domain has been created"})
}
func (ms *MockServer) deleteDomain(w http.ResponseWriter, r *http.Request) {
result := ms.domainList[:0]
for _, domain := range ms.domainList {
if domain.Domain.Name == chi.URLParam(r, "domain") {
continue
}
result = append(result, domain)
}
if len(result) != len(ms.domainList) {
toJSON(w, okResp{Message: "success"})
ms.domainList = result
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) getConnection(w http.ResponseWriter, r *http.Request) {
for _, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
resp := domainConnectionResponse{
Connection: *d.Connection,
}
toJSON(w, resp)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) updateConnection(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
ms.domainList[i].Connection = &DomainConnection{
RequireTLS: stringToBool(r.FormValue("require_tls")),
SkipVerification: stringToBool(r.FormValue("skip_verification")),
}
toJSON(w, okResp{Message: "Domain connection settings have been updated, may take 10 minutes to fully propagate"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) getTracking(w http.ResponseWriter, r *http.Request) {
for _, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
resp := domainTrackingResponse{
Tracking: *d.Tracking,
}
toJSON(w, resp)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) updateClickTracking(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
ms.domainList[i].Tracking.Click.Active = stringToBool(r.FormValue("active"))
toJSON(w, okResp{Message: "Domain tracking settings have been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) updateOpenTracking(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
ms.domainList[i].Tracking.Open.Active = stringToBool(r.FormValue("active"))
toJSON(w, okResp{Message: "Domain tracking settings have been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) updateUnsubTracking(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
ms.domainList[i].Tracking.Unsubscribe.Active = stringToBool(r.FormValue("active"))
if len(r.FormValue("html_footer")) != 0 {
ms.domainList[i].Tracking.Unsubscribe.HTMLFooter = r.FormValue("html_footer")
}
if len(r.FormValue("text_footer")) != 0 {
ms.domainList[i].Tracking.Unsubscribe.TextFooter = r.FormValue("text_footer")
}
toJSON(w, okResp{Message: "Domain tracking settings have been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) getTagLimits(w http.ResponseWriter, r *http.Request) {
for _, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
if d.TagLimits == nil {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "no limits defined for domain"})
return
}
toJSON(w, d.TagLimits)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}

242
vendor/github.com/mailgun/mailgun-go/v4/mock_events.go generated vendored Normal file
View File

@@ -0,0 +1,242 @@
package mailgun
import (
"net/http"
"net/url"
"time"
"github.com/go-chi/chi"
"github.com/mailgun/mailgun-go/v4/events"
)
func (ms *MockServer) addEventRoutes(r chi.Router) {
r.Get("/{domain}/events", ms.listEvents)
var (
tags = []string{"tag1", "tag2"}
recipients = []string{"one@mailgun.test", "two@mailgun.test"}
recipientDomain = "mailgun.test"
timeStamp = TimeToFloat(time.Now().UTC())
ipAddress = "192.168.1.1"
message = events.Message{Headers: events.MessageHeaders{MessageID: "1234"}}
clientInfo = events.ClientInfo{
AcceptLanguage: "EN",
ClientName: "Firefox",
ClientOS: "OS X",
ClientType: "browser",
DeviceType: "desktop",
IP: "8.8.8.8",
UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0",
}
geoLocation = events.GeoLocation{
City: "San Antonio",
Country: "US",
Region: "TX",
}
)
// AcceptedNoAuth
accepted := new(events.Accepted)
accepted.ID = randomString(16, "ID-")
accepted.Message.Headers.MessageID = accepted.ID
accepted.Name = events.EventAccepted
accepted.Tags = tags
accepted.Timestamp = timeStamp
accepted.Recipient = recipients[0]
accepted.RecipientDomain = recipientDomain
accepted.Flags = events.Flags{
IsAuthenticated: false,
}
ms.events = append(ms.events, accepted)
// AcceptedAuth
accepted = new(events.Accepted)
accepted.ID = randomString(16, "ID-")
accepted.Message.Headers.MessageID = accepted.ID
accepted.Name = events.EventAccepted
accepted.Tags = tags
accepted.Timestamp = timeStamp
accepted.Recipient = recipients[0]
accepted.RecipientDomain = recipientDomain
accepted.Campaigns = []events.Campaign{
{ID: "test-id", Name: "test"},
}
accepted.Flags = events.Flags{
IsAuthenticated: true,
}
ms.events = append(ms.events, accepted)
// DeliveredSMTP
delivered := new(events.Delivered)
delivered.ID = randomString(16, "ID-")
delivered.Message.Headers.MessageID = delivered.ID
delivered.Name = events.EventDelivered
delivered.Tags = tags
delivered.Timestamp = timeStamp
delivered.Recipient = recipients[0]
delivered.RecipientDomain = recipientDomain
delivered.DeliveryStatus.Message = "We sent an email Yo"
delivered.Envelope = events.Envelope{
Transport: "smtp",
SendingIP: ipAddress,
}
delivered.Flags = events.Flags{
IsAuthenticated: true,
}
ms.events = append(ms.events, delivered)
// DeliveredHTTP
delivered = new(events.Delivered)
delivered.ID = randomString(16, "ID-")
delivered.Message.Headers.MessageID = delivered.ID
delivered.Name = events.EventDelivered
delivered.Tags = tags
delivered.Timestamp = timeStamp
delivered.Recipient = recipients[0]
delivered.RecipientDomain = recipientDomain
delivered.DeliveryStatus.Message = "We sent an email Yo"
delivered.Envelope = events.Envelope{
Transport: "http",
SendingIP: ipAddress,
}
delivered.Flags = events.Flags{
IsAuthenticated: true,
}
ms.events = append(ms.events, delivered)
// Stored
stored := new(events.Stored)
stored.ID = randomString(16, "ID-")
stored.Name = events.EventStored
stored.Tags = tags
stored.Timestamp = timeStamp
stored.Storage.URL = "http://mailgun.text/some/url"
ms.events = append(ms.events, stored)
// Clicked
for _, recipient := range recipients {
clicked := new(events.Clicked)
clicked.ID = randomString(16, "ID-")
clicked.Name = events.EventClicked
clicked.Message = message
clicked.Tags = tags
clicked.Recipient = recipient
clicked.ClientInfo = clientInfo
clicked.GeoLocation = geoLocation
clicked.Timestamp = timeStamp
ms.events = append(ms.events, clicked)
}
clicked := new(events.Clicked)
clicked.ID = randomString(16, "ID-")
clicked.Name = events.EventClicked
clicked.Message = message
clicked.Tags = tags
clicked.Recipient = recipients[0]
clicked.ClientInfo = clientInfo
clicked.GeoLocation = geoLocation
clicked.Timestamp = timeStamp
ms.events = append(ms.events, clicked)
// Opened
for _, recipient := range recipients {
opened := new(events.Opened)
opened.ID = randomString(16, "ID-")
opened.Name = events.EventOpened
opened.Message = message
opened.Tags = tags
opened.Recipient = recipient
opened.ClientInfo = clientInfo
opened.GeoLocation = geoLocation
opened.Timestamp = timeStamp
ms.events = append(ms.events, opened)
}
opened := new(events.Opened)
opened.ID = randomString(16, "ID-")
opened.Name = events.EventOpened
opened.Message = message
opened.Tags = tags
opened.Recipient = recipients[0]
opened.ClientInfo = clientInfo
opened.GeoLocation = geoLocation
opened.Timestamp = timeStamp
ms.events = append(ms.events, opened)
// Unsubscribed
for _, recipient := range recipients {
unsub := new(events.Unsubscribed)
unsub.ID = randomString(16, "ID-")
unsub.Name = events.EventUnsubscribed
unsub.Tags = tags
unsub.Recipient = recipient
unsub.ClientInfo = clientInfo
unsub.GeoLocation = geoLocation
unsub.Timestamp = timeStamp
ms.events = append(ms.events, unsub)
}
// Complained
for _, recipient := range recipients {
complained := new(events.Complained)
complained.ID = randomString(16, "ID-")
complained.Name = events.EventComplained
complained.Tags = tags
complained.Recipient = recipient
complained.Timestamp = timeStamp
ms.events = append(ms.events, complained)
}
}
type eventsResponse struct {
Items []Event `json:"items"`
Paging Paging `json:"paging"`
}
func (ms *MockServer) listEvents(w http.ResponseWriter, r *http.Request) {
var idx []string
for _, e := range ms.events {
idx = append(idx, e.GetID())
}
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
start, end := pageOffsets(idx, r.FormValue("page"), r.FormValue("address"), limit)
var nextAddress, prevAddress string
var results []Event
if start != end {
results = ms.events[start:end]
nextAddress = results[len(results)-1].GetID()
prevAddress = results[0].GetID()
} else {
results = []Event{}
nextAddress = r.FormValue("address")
prevAddress = r.FormValue("address")
}
resp := eventsResponse{
Paging: Paging{
First: getPageURL(r, url.Values{
"page": []string{"first"},
}),
Last: getPageURL(r, url.Values{
"page": []string{"last"},
}),
Next: getPageURL(r, url.Values{
"page": []string{"next"},
"address": []string{nextAddress},
}),
Previous: getPageURL(r, url.Values{
"page": []string{"prev"},
"address": []string{prevAddress},
}),
},
Items: results,
}
toJSON(w, resp)
}

View File

@@ -0,0 +1,48 @@
package mailgun
import (
"net/http"
"strconv"
"github.com/go-chi/chi"
)
func (ms *MockServer) addExportRoutes(r chi.Router) {
r.Post("/exports", ms.postExports)
r.Get("/exports", ms.listExports)
r.Get("/exports/{id}", ms.getExport)
r.Get("/exports/{id}/download_url", ms.getExportLink)
}
func (ms *MockServer) postExports(w http.ResponseWriter, r *http.Request) {
e := Export{
ID: strconv.Itoa(len(ms.exportList)),
URL: r.FormValue("url"),
Status: "complete",
}
ms.exportList = append(ms.exportList, e)
toJSON(w, okResp{Message: "success"})
}
func (ms *MockServer) listExports(w http.ResponseWriter, _ *http.Request) {
toJSON(w, ExportList{
Items: ms.exportList,
})
}
func (ms *MockServer) getExport(w http.ResponseWriter, r *http.Request) {
for _, export := range ms.exportList {
if export.ID == chi.URLParam(r, "id") {
toJSON(w, export)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "export not found"})
}
func (ms *MockServer) getExportLink(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "/some/s3/url")
w.WriteHeader(http.StatusFound)
}

65
vendor/github.com/mailgun/mailgun-go/v4/mock_ips.go generated vendored Normal file
View File

@@ -0,0 +1,65 @@
package mailgun
import (
"net/http"
"github.com/go-chi/chi"
)
func (ms *MockServer) addIPRoutes(r chi.Router) {
r.Get("/ips", ms.listIPS)
r.Get("/ips/{ip}", ms.getIPAddress)
r.Route("/domains/{domain}/ips", func(r chi.Router) {
r.Get("/", ms.listDomainIPS)
r.Get("/{ip}", ms.getIPAddress)
r.Post("/", ms.postDomainIPS)
r.Delete("/{ip}", ms.deleteDomainIPS)
})
}
func (ms *MockServer) listIPS(w http.ResponseWriter, _ *http.Request) {
toJSON(w, ipAddressListResponse{
TotalCount: 2,
Items: []string{"172.0.0.1", "192.168.1.1"},
})
}
func (ms *MockServer) getIPAddress(w http.ResponseWriter, r *http.Request) {
toJSON(w, IPAddress{
IP: chi.URLParam(r, "ip"),
RDNS: "luna.mailgun.net",
Dedicated: true,
})
}
func (ms *MockServer) listDomainIPS(w http.ResponseWriter, _ *http.Request) {
toJSON(w, ipAddressListResponse{
TotalCount: 2,
Items: ms.domainIPS,
})
}
func (ms *MockServer) postDomainIPS(w http.ResponseWriter, r *http.Request) {
ms.domainIPS = append(ms.domainIPS, r.FormValue("ip"))
toJSON(w, okResp{Message: "success"})
}
func (ms *MockServer) deleteDomainIPS(w http.ResponseWriter, r *http.Request) {
result := ms.domainIPS[:0]
for _, ip := range ms.domainIPS {
if ip == chi.URLParam(r, "ip") {
continue
}
result = append(result, ip)
}
if len(result) != len(ms.domainIPS) {
toJSON(w, okResp{Message: "success"})
ms.domainIPS = result
return
}
// Not the actual error returned by the mailgun API
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "ip not found"})
}

View File

@@ -0,0 +1,389 @@
package mailgun
import (
"encoding/json"
"net/http"
"net/url"
"time"
"github.com/go-chi/chi"
)
type mailingListContainer struct {
MailingList MailingList
Members []Member
}
func (ms *MockServer) addMailingListRoutes(r chi.Router) {
r.Get("/lists/pages", ms.listMailingLists)
r.Get("/lists/{address}", ms.getMailingList)
r.Post("/lists", ms.createMailingList)
r.Put("/lists/{address}", ms.updateMailingList)
r.Delete("/lists/{address}", ms.deleteMailingList)
r.Get("/lists/{address}/members/pages", ms.listMembers)
r.Get("/lists/{address}/members/{member}", ms.getMember)
r.Post("/lists/{address}/members", ms.createMember)
r.Put("/lists/{address}/members/{member}", ms.updateMember)
r.Delete("/lists/{address}/members/{member}", ms.deleteMember)
r.Post("/lists/{address}/members.json", ms.bulkCreate)
ms.mailingList = append(ms.mailingList, mailingListContainer{
MailingList: MailingList{
AccessLevel: "everyone",
Address: "foo@mailgun.test",
CreatedAt: RFC2822Time(time.Now().UTC()),
Description: "Mailgun developers list",
MembersCount: 1,
Name: "",
},
Members: []Member{
{
Address: "dev@samples.mailgun.org",
Name: "Developer",
},
},
})
}
func (ms *MockServer) listMailingLists(w http.ResponseWriter, r *http.Request) {
var list []MailingList
var idx []string
for _, ml := range ms.mailingList {
list = append(list, ml.MailingList)
idx = append(idx, ml.MailingList.Address)
}
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
start, end := pageOffsets(idx, r.FormValue("page"), r.FormValue("address"), limit)
results := list[start:end]
if len(results) == 0 {
toJSON(w, listsResponse{})
return
}
resp := listsResponse{
Paging: Paging{
First: getPageURL(r, url.Values{
"page": []string{"first"},
}),
Last: getPageURL(r, url.Values{
"page": []string{"last"},
}),
Next: getPageURL(r, url.Values{
"page": []string{"next"},
"address": []string{results[len(results)-1].Address},
}),
Previous: getPageURL(r, url.Values{
"page": []string{"prev"},
"address": []string{results[0].Address},
}),
},
Items: results,
}
toJSON(w, resp)
}
func (ms *MockServer) getMailingList(w http.ResponseWriter, r *http.Request) {
for _, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
toJSON(w, mailingListResponse{MailingList: ml.MailingList})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
}
func (ms *MockServer) deleteMailingList(w http.ResponseWriter, r *http.Request) {
result := ms.mailingList[:0]
for _, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
continue
}
result = append(result, ml)
}
if len(result) != len(ms.mailingList) {
toJSON(w, okResp{Message: "success"})
ms.mailingList = result
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
}
func (ms *MockServer) updateMailingList(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.mailingList {
if d.MailingList.Address == chi.URLParam(r, "address") {
if r.FormValue("address") != "" {
ms.mailingList[i].MailingList.Address = r.FormValue("address")
}
if r.FormValue("name") != "" {
ms.mailingList[i].MailingList.Name = r.FormValue("name")
}
if r.FormValue("description") != "" {
ms.mailingList[i].MailingList.Description = r.FormValue("description")
}
if r.FormValue("access_level") != "" {
ms.mailingList[i].MailingList.AccessLevel = AccessLevel(r.FormValue("access_level"))
}
toJSON(w, okResp{Message: "Mailing list member has been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
}
func (ms *MockServer) createMailingList(w http.ResponseWriter, r *http.Request) {
ms.mailingList = append(ms.mailingList, mailingListContainer{
MailingList: MailingList{
CreatedAt: RFC2822Time(time.Now().UTC()),
Name: r.FormValue("name"),
Address: r.FormValue("address"),
Description: r.FormValue("description"),
AccessLevel: AccessLevel(r.FormValue("access_level")),
},
})
toJSON(w, okResp{Message: "Mailing list has been created"})
}
func (ms *MockServer) listMembers(w http.ResponseWriter, r *http.Request) {
var list []Member
var idx []string
var found bool
for _, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
found = true
for _, member := range ml.Members {
list = append(list, member)
idx = append(idx, member.Address)
}
}
}
if !found {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
start, end := pageOffsets(idx, r.FormValue("page"), r.FormValue("address"), limit)
results := list[start:end]
if len(results) == 0 {
toJSON(w, memberListResponse{})
return
}
resp := memberListResponse{
Paging: Paging{
First: getPageURL(r, url.Values{
"page": []string{"first"},
}),
Last: getPageURL(r, url.Values{
"page": []string{"last"},
}),
Next: getPageURL(r, url.Values{
"page": []string{"next"},
"address": []string{results[len(results)-1].Address},
}),
Previous: getPageURL(r, url.Values{
"page": []string{"prev"},
"address": []string{results[0].Address},
}),
},
Lists: results,
}
toJSON(w, resp)
}
func (ms *MockServer) getMember(w http.ResponseWriter, r *http.Request) {
var found bool
for _, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
found = true
for _, member := range ml.Members {
if member.Address == chi.URLParam(r, "member") {
toJSON(w, memberResponse{Member: member})
return
}
}
}
}
if !found {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "member not found"})
}
func (ms *MockServer) deleteMember(w http.ResponseWriter, r *http.Request) {
idx := -1
for i, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
idx = i
}
}
if idx == -1 {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
result := ms.mailingList[idx].Members[:0]
for _, m := range ms.mailingList[idx].Members {
if m.Address == chi.URLParam(r, "member") {
continue
}
result = append(result, m)
}
if len(result) != len(ms.mailingList[idx].Members) {
toJSON(w, okResp{Message: "Mailing list member has been deleted"})
ms.mailingList[idx].Members = result
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "member not found"})
}
func (ms *MockServer) updateMember(w http.ResponseWriter, r *http.Request) {
idx := -1
for i, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
idx = i
}
}
if idx == -1 {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
for i, m := range ms.mailingList[idx].Members {
if m.Address == chi.URLParam(r, "member") {
if r.FormValue("address") != "" {
ms.mailingList[idx].Members[i].Address = parseAddress(r.FormValue("address"))
}
if r.FormValue("name") != "" {
ms.mailingList[idx].Members[i].Name = r.FormValue("name")
}
if r.FormValue("vars") != "" {
ms.mailingList[idx].Members[i].Vars = stringToMap(r.FormValue("vars"))
}
if r.FormValue("subscribed") != "" {
sub := stringToBool(r.FormValue("subscribed"))
ms.mailingList[idx].Members[i].Subscribed = &sub
}
toJSON(w, okResp{Message: "Mailing list member has been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "member not found"})
}
func (ms *MockServer) createMember(w http.ResponseWriter, r *http.Request) {
idx := -1
for i, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
idx = i
}
}
if idx == -1 {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
sub := stringToBool(r.FormValue("subscribed"))
if len(ms.mailingList[idx].Members) != 0 {
for i, m := range ms.mailingList[idx].Members {
if m.Address == r.FormValue("address") {
if !stringToBool(r.FormValue("upsert")) {
w.WriteHeader(http.StatusConflict)
toJSON(w, okResp{Message: "member already exists"})
return
}
ms.mailingList[idx].Members[i].Address = parseAddress(r.FormValue("address"))
ms.mailingList[idx].Members[i].Name = r.FormValue("name")
ms.mailingList[idx].Members[i].Vars = stringToMap(r.FormValue("vars"))
ms.mailingList[idx].Members[i].Subscribed = &sub
break
}
}
}
ms.mailingList[idx].Members = append(ms.mailingList[idx].Members, Member{
Name: r.FormValue("name"),
Address: parseAddress(r.FormValue("address")),
Vars: stringToMap(r.FormValue("vars")),
Subscribed: &sub,
})
toJSON(w, okResp{Message: "Mailing list member has been created"})
}
func (ms *MockServer) bulkCreate(w http.ResponseWriter, r *http.Request) {
idx := -1
for i, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
idx = i
}
}
if idx == -1 {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
var bulkList []Member
if err := json.Unmarshal([]byte(r.FormValue("members")), &bulkList); err != nil {
w.WriteHeader(http.StatusInternalServerError)
toJSON(w, okResp{Message: "while un-marshalling 'members' param - " + err.Error()})
return
}
BULK:
for _, member := range bulkList {
member.Address = parseAddress(member.Address)
if len(ms.mailingList[idx].Members) != 0 {
for i, m := range ms.mailingList[idx].Members {
if m.Address == member.Address {
if !stringToBool(r.FormValue("upsert")) {
w.WriteHeader(http.StatusConflict)
toJSON(w, okResp{Message: "member already exists"})
return
}
ms.mailingList[idx].Members[i] = member
continue BULK
}
}
}
ms.mailingList[idx].Members = append(ms.mailingList[idx].Members, member)
}
toJSON(w, okResp{Message: "Mailing list has been updated"})
}

View File

@@ -0,0 +1,123 @@
package mailgun
import (
"net/http"
"net/mail"
"strings"
"time"
"github.com/go-chi/chi"
"github.com/mailgun/mailgun-go/v4/events"
)
func (ms *MockServer) addMessagesRoutes(r chi.Router) {
r.Post("/{domain}/messages", ms.createMessages)
// This path is made up; it could be anything as the storage url could change over time
r.Get("/se.storage.url/messages/{id}", ms.getStoredMessages)
r.Post("/se.storage.url/messages/{id}", ms.sendStoredMessages)
}
// TODO: This implementation doesn't support multiple recipients
func (ms *MockServer) createMessages(w http.ResponseWriter, r *http.Request) {
to, err := mail.ParseAddress(r.FormValue("to"))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: "invalid 'to' address"})
return
}
id := randomString(16, "ID-")
switch to.Address {
case "stored@mailgun.test":
stored := new(events.Stored)
stored.Name = events.EventStored
stored.Timestamp = TimeToFloat(time.Now().UTC())
stored.ID = id
stored.Storage.URL = ms.URL() + "/se.storage.url/messages/" + id
stored.Storage.Key = id
stored.Message.Headers = events.MessageHeaders{
Subject: r.FormValue("subject"),
From: r.FormValue("from"),
To: to.Address,
MessageID: id,
}
stored.Message.Recipients = []string{
r.FormValue("to"),
}
stored.Message.Size = 10
stored.Flags = events.Flags{
IsTestMode: false,
}
ms.events = append(ms.events, stored)
default:
accepted := new(events.Accepted)
accepted.Name = events.EventAccepted
accepted.ID = id
accepted.Timestamp = TimeToFloat(time.Now().UTC())
accepted.Message.Headers.From = r.FormValue("from")
accepted.Message.Headers.To = r.FormValue("to")
accepted.Message.Headers.MessageID = accepted.ID
accepted.Message.Headers.Subject = r.FormValue("subject")
accepted.Recipient = r.FormValue("to")
accepted.RecipientDomain = strings.Split(to.Address, "@")[1]
accepted.Flags = events.Flags{
IsAuthenticated: true,
}
ms.events = append(ms.events, accepted)
}
toJSON(w, okResp{ID: "<" + id + ">", Message: "Queued. Thank you."})
}
func (ms *MockServer) getStoredMessages(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
// Find our stored event
var stored *events.Stored
for _, event := range ms.events {
if event.GetID() == id {
stored = event.(*events.Stored)
}
}
if stored == nil {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "not found"})
}
toJSON(w, StoredMessage{
Recipients: strings.Join(stored.Message.Recipients, ","),
Sender: stored.Message.Headers.From,
Subject: stored.Message.Headers.Subject,
From: stored.Message.Headers.From,
MessageHeaders: [][]string{
{"Sender", stored.Message.Headers.From},
{"To", stored.Message.Headers.To},
{"Subject", stored.Message.Headers.Subject},
{"Content-Type", "text/plain"},
},
})
}
func (ms *MockServer) sendStoredMessages(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
// Find our stored event
var stored *events.Stored
for _, event := range ms.events {
if event.GetID() == id {
stored = event.(*events.Stored)
}
}
if stored == nil {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "not found"})
}
// DO NOTHING
toJSON(w, okResp{ID: "<" + id + ">", Message: "Queued. Thank you."})
}

139
vendor/github.com/mailgun/mailgun-go/v4/mock_routes.go generated vendored Normal file
View File

@@ -0,0 +1,139 @@
package mailgun
import (
"fmt"
"github.com/go-chi/chi"
"net/http"
"time"
)
type routeResponse struct {
Route Route `json:"route"`
}
func (ms *MockServer) addRoutes(r chi.Router) {
r.Post("/routes", ms.createRoute)
r.Get("/routes", ms.listRoutes)
r.Get("/routes/{id}", ms.getRoute)
r.Put("/routes/{id}", ms.updateRoute)
r.Delete("/routes/{id}", ms.deleteRoute)
for i := 0; i < 10; i++ {
ms.routeList = append(ms.routeList, Route{
Id: randomString(10, "ID-"),
Priority: 0,
Description: fmt.Sprintf("Sample Route %d", i),
Actions: []string{
`forward("http://myhost.com/messages/")`,
`stop()`,
},
Expression: `match_recipient(".*@samples.mailgun.org")`,
})
}
}
func (ms *MockServer) listRoutes(w http.ResponseWriter, r *http.Request) {
skip := stringToInt(r.FormValue("skip"))
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
if skip > len(ms.routeList) {
skip = len(ms.routeList)
}
end := limit + skip
if end > len(ms.routeList) {
end = len(ms.routeList)
}
// If we are at the end of the list
if skip == end {
toJSON(w, routesListResponse{
TotalCount: len(ms.routeList),
Items: []Route{},
})
return
}
toJSON(w, routesListResponse{
TotalCount: len(ms.routeList),
Items: ms.routeList[skip:end],
})
}
func (ms *MockServer) getRoute(w http.ResponseWriter, r *http.Request) {
for _, item := range ms.routeList {
if item.Id == chi.URLParam(r, "id") {
toJSON(w, routeResponse{Route: item})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "route not found"})
}
func (ms *MockServer) createRoute(w http.ResponseWriter, r *http.Request) {
if r.FormValue("action") == "" {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: "'action' parameter is required"})
return
}
ms.routeList = append(ms.routeList, Route{
CreatedAt: RFC2822Time(time.Now().UTC()),
Id: randomString(10, "ID-"),
Priority: stringToInt(r.FormValue("priority")),
Description: r.FormValue("description"),
Expression: r.FormValue("expression"),
Actions: r.Form["action"],
})
toJSON(w, createRouteResp{
Message: "Route has been created",
Route: ms.routeList[len(ms.routeList)-1],
})
}
func (ms *MockServer) updateRoute(w http.ResponseWriter, r *http.Request) {
for i, item := range ms.routeList {
if item.Id == chi.URLParam(r, "id") {
if r.FormValue("action") != "" {
ms.routeList[i].Actions = r.Form["action"]
}
if r.FormValue("priority") != "" {
ms.routeList[i].Priority = stringToInt(r.FormValue("priority"))
}
if r.FormValue("description") != "" {
ms.routeList[i].Description = r.FormValue("description")
}
if r.FormValue("expression") != "" {
ms.routeList[i].Expression = r.FormValue("expression")
}
toJSON(w, ms.routeList[i])
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "route not found"})
}
func (ms *MockServer) deleteRoute(w http.ResponseWriter, r *http.Request) {
result := ms.routeList[:0]
for _, item := range ms.routeList {
if item.Id == chi.URLParam(r, "id") {
continue
}
result = append(result, item)
}
if len(result) != len(ms.domainList) {
toJSON(w, okResp{Message: "success"})
ms.routeList = result
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "route not found"})
}

View File

@@ -0,0 +1,76 @@
package mailgun
import (
"net/http"
"net/mail"
"strings"
"github.com/go-chi/chi"
)
func (ms *MockServer) addValidationRoutes(r chi.Router) {
r.Get("/v3/address/validate", ms.validateEmail)
r.Get("/v3/address/parse", ms.parseEmail)
r.Get("/v3/address/private/validate", ms.validateEmail)
r.Get("/v3/address/private/parse", ms.parseEmail)
r.Get("/v4/address/validate", ms.validateEmailV4)
}
func (ms *MockServer) validateEmailV4(w http.ResponseWriter, r *http.Request) {
if r.FormValue("address") == "" {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: "'address' parameter is required"})
return
}
var results v4EmailValidationResp
parts, err := mail.ParseAddress(r.FormValue("address"))
if err == nil {
results.IsValid = true
results.Parts.Domain = strings.Split(parts.Address, "@")[1]
results.Parts.LocalPart = strings.Split(parts.Address, "@")[0]
results.Parts.DisplayName = parts.Name
}
results.Reason = []string{"no-reason"}
toJSON(w, results)
}
func (ms *MockServer) validateEmail(w http.ResponseWriter, r *http.Request) {
if r.FormValue("address") == "" {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: "'address' parameter is required"})
return
}
var results EmailVerification
parts, err := mail.ParseAddress(r.FormValue("address"))
if err == nil {
results.IsValid = true
results.Parts.Domain = strings.Split(parts.Address, "@")[1]
results.Parts.LocalPart = strings.Split(parts.Address, "@")[0]
results.Parts.DisplayName = parts.Name
}
results.Reason = "no-reason"
toJSON(w, results)
}
func (ms *MockServer) parseEmail(w http.ResponseWriter, r *http.Request) {
if r.FormValue("addresses") == "" {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: "'addresses' parameter is required"})
return
}
addresses := strings.Split(r.FormValue("addresses"), ",")
var results addressParseResult
for _, address := range addresses {
_, err := mail.ParseAddress(address)
if err != nil {
results.Unparseable = append(results.Unparseable, address)
} else {
results.Parsed = append(results.Parsed, address)
}
}
toJSON(w, results)
}

View File

@@ -0,0 +1,83 @@
package mailgun
import (
"net/http"
"github.com/go-chi/chi"
)
func (ms *MockServer) addWebhookRoutes(r chi.Router) {
r.Route("/domains/{domain}/webhooks", func(r chi.Router) {
r.Get("/", ms.listWebHooks)
r.Post("/", ms.postWebHook)
r.Get("/{webhook}", ms.getWebHook)
r.Put("/{webhook}", ms.putWebHook)
r.Delete("/{webhook}", ms.deleteWebHook)
})
ms.webhooks = WebHooksListResponse{
Webhooks: map[string]UrlOrUrls{
"new-webhook": {
Urls: []string{"http://example.com/new"},
},
"legacy-webhook": {
Url: "http://example.com/legacy",
},
},
}
}
func (ms *MockServer) listWebHooks(w http.ResponseWriter, _ *http.Request) {
toJSON(w, ms.webhooks)
}
func (ms *MockServer) getWebHook(w http.ResponseWriter, r *http.Request) {
resp := WebHookResponse{
Webhook: UrlOrUrls{
Urls: ms.webhooks.Webhooks[chi.URLParam(r, "webhook")].Urls,
},
}
toJSON(w, resp)
}
func (ms *MockServer) postWebHook(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: err.Error()})
return
}
var urls []string
for _, url := range r.Form["url"] {
urls = append(urls, url)
}
ms.webhooks.Webhooks[r.FormValue("id")] = UrlOrUrls{Urls: urls}
toJSON(w, okResp{Message: "success"})
}
func (ms *MockServer) putWebHook(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: err.Error()})
return
}
var urls []string
for _, url := range r.Form["url"] {
urls = append(urls, url)
}
ms.webhooks.Webhooks[chi.URLParam(r, "webhook")] = UrlOrUrls{Urls: urls}
toJSON(w, okResp{Message: "success"})
}
func (ms *MockServer) deleteWebHook(w http.ResponseWriter, r *http.Request) {
_, ok := ms.webhooks.Webhooks[chi.URLParam(r, "webhook")]
if !ok {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "webhook not found"})
}
delete(ms.webhooks.Webhooks, chi.URLParam(r, "webhook"))
toJSON(w, okResp{Message: "success"})
}

97
vendor/github.com/mailgun/mailgun-go/v4/parse.go generated vendored Normal file
View File

@@ -0,0 +1,97 @@
package mailgun
import (
"fmt"
"reflect"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/mailgun/mailgun-go/v4/events"
)
// All events returned by the EventIterator conform to this interface
type Event interface {
GetName() string
SetName(name string)
GetTimestamp() time.Time
SetTimestamp(time.Time)
GetID() string
SetID(id string)
}
// A list of all JSON event types returned by the /events API
var EventNames = map[string]func() Event{
"accepted": new_(events.Accepted{}),
"clicked": new_(events.Clicked{}),
"complained": new_(events.Complained{}),
"delivered": new_(events.Delivered{}),
"failed": new_(events.Failed{}),
"opened": new_(events.Opened{}),
"rejected": new_(events.Rejected{}),
"stored": new_(events.Stored{}),
"unsubscribed": new_(events.Unsubscribed{}),
"list_member_uploaded": new_(events.ListMemberUploaded{}),
"list_member_upload_error": new_(events.ListMemberUploadError{}),
"list_uploaded": new_(events.ListUploaded{}),
}
// new_ is a universal event "constructor".
func new_(e interface{}) func() Event {
typ := reflect.TypeOf(e)
return func() Event {
return reflect.New(typ).Interface().(Event)
}
}
func parseResponse(raw []byte) ([]Event, error) {
var resp events.Response
if err := jsoniter.Unmarshal(raw, &resp); err != nil {
return nil, fmt.Errorf("failed to un-marshall event.Response: %s", err)
}
var result []Event
for _, value := range resp.Items {
event, err := ParseEvent(value)
if err != nil {
return nil, fmt.Errorf("while parsing event: %s", err)
}
result = append(result, event)
}
return result, nil
}
// Given a slice of events.RawJSON events return a slice of Event for each parsed event
func ParseEvents(raw []events.RawJSON) ([]Event, error) {
var result []Event
for _, value := range raw {
event, err := ParseEvent(value)
if err != nil {
return nil, fmt.Errorf("while parsing event: %s", err)
}
result = append(result, event)
}
return result, nil
}
// Parse converts raw bytes data into an event struct. Can accept events.RawJSON as input
func ParseEvent(raw []byte) (Event, error) {
// Try to recognize the event first.
var e events.EventName
if err := jsoniter.Unmarshal(raw, &e); err != nil {
return nil, fmt.Errorf("failed to recognize event: %v", err)
}
// Get the event "constructor" from the map.
newEvent, ok := EventNames[e.GetName()]
if !ok {
return nil, fmt.Errorf("unsupported event: '%s'", e.GetName())
}
event := newEvent()
// Parse the known event.
if err := jsoniter.Unmarshal(raw, event); err != nil {
return nil, fmt.Errorf("failed to parse event '%s': %v", e.GetName(), err)
}
return event, nil
}

42
vendor/github.com/mailgun/mailgun-go/v4/recipients.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package mailgun
import "fmt"
import "strings"
type Recipient struct {
Name string `json:"-"`
Email string `json:"-"`
}
func (r Recipient) String() string {
if r.Name != "" {
return fmt.Sprintf("%s <%s>", r.Name, r.Email)
}
return r.Email
}
// MarshalText satisfies TextMarshaler
func (r Recipient) MarshalText() ([]byte, error) {
return []byte(r.String()), nil
}
// UnmarshalText satisfies TextUnmarshaler
func (r *Recipient) UnmarshalText(text []byte) error {
s := string(text)
if s[len(s)-1:] != ">" {
*r = Recipient{Email: s}
return nil
}
i := strings.Index(s, "<")
// at least 1 char followed by a space
if i < 2 {
return fmt.Errorf("malformed recipient string '%s'", s)
}
*r = Recipient{
Name: strings.TrimSpace(s[:i]),
Email: s[i+1 : len(s)-1],
}
return nil
}

163
vendor/github.com/mailgun/mailgun-go/v4/rest_shim.go generated vendored Normal file
View File

@@ -0,0 +1,163 @@
package mailgun
import (
"context"
"fmt"
)
// The MailgunGoUserAgent identifies the client to the server, for logging purposes.
// In the event of problems requiring a human administrator's assistance,
// this user agent allows them to identify the client from human-generated activity.
const MailgunGoUserAgent = "mailgun-go/" + Version
// This error will be returned whenever a Mailgun API returns an error response.
// Your application can check the Actual field to see the actual HTTP response code returned.
// URL contains the base URL accessed, sans any query parameters.
type UnexpectedResponseError struct {
Expected []int
Actual int
URL string
Data []byte
}
// String() converts the error into a human-readable, logfmt-compliant string.
// See http://godoc.org/github.com/kr/logfmt for details on logfmt formatting.
func (e *UnexpectedResponseError) String() string {
return fmt.Sprintf("UnexpectedResponseError URL=%s ExpectedOneOf=%#v Got=%d Error: %s", e.URL, e.Expected, e.Actual, string(e.Data))
}
// Error() performs as String().
func (e *UnexpectedResponseError) Error() string {
return e.String()
}
// newError creates a new error condition to be returned.
func newError(url string, expected []int, got *httpResponse) error {
return &UnexpectedResponseError{
URL: url,
Expected: expected,
Actual: got.Code,
Data: got.Data,
}
}
// notGood searches a list of response codes (the haystack) for a matching entry (the needle).
// If found, the response code is considered good, and thus false is returned.
// Otherwise true is returned.
func notGood(needle int, haystack []int) bool {
for _, i := range haystack {
if needle == i {
return false
}
}
return true
}
// expected denotes the expected list of known-good HTTP response codes possible from the Mailgun API.
var expected = []int{200, 202, 204}
// makeRequest shim performs a generic request, checking for a positive outcome.
// See simplehttp.MakeRequest for more details.
func makeRequest(ctx context.Context, r *httpRequest, method string, p payload) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makeRequest(ctx, method, p)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// getResponseFromJSON shim performs a GET request, checking for a positive outcome.
// See simplehttp.GetResponseFromJSON for more details.
func getResponseFromJSON(ctx context.Context, r *httpRequest, v interface{}) error {
r.addHeader("User-Agent", MailgunGoUserAgent)
response, err := r.makeGetRequest(ctx)
if err != nil {
return err
}
if notGood(response.Code, expected) {
return newError(r.URL, expected, response)
}
return response.parseFromJSON(v)
}
// postResponseFromJSON shim performs a POST request, checking for a positive outcome.
// See simplehttp.PostResponseFromJSON for more details.
func postResponseFromJSON(ctx context.Context, r *httpRequest, p payload, v interface{}) error {
r.addHeader("User-Agent", MailgunGoUserAgent)
response, err := r.makePostRequest(ctx, p)
if err != nil {
return err
}
if notGood(response.Code, expected) {
return newError(r.URL, expected, response)
}
return response.parseFromJSON(v)
}
// putResponseFromJSON shim performs a PUT request, checking for a positive outcome.
// See simplehttp.PutResponseFromJSON for more details.
func putResponseFromJSON(ctx context.Context, r *httpRequest, p payload, v interface{}) error {
r.addHeader("User-Agent", MailgunGoUserAgent)
response, err := r.makePutRequest(ctx, p)
if err != nil {
return err
}
if notGood(response.Code, expected) {
return newError(r.URL, expected, response)
}
return response.parseFromJSON(v)
}
// makeGetRequest shim performs a GET request, checking for a positive outcome.
// See simplehttp.MakeGetRequest for more details.
func makeGetRequest(ctx context.Context, r *httpRequest) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makeGetRequest(ctx)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// makePostRequest shim performs a POST request, checking for a positive outcome.
// See simplehttp.MakePostRequest for more details.
func makePostRequest(ctx context.Context, r *httpRequest, p payload) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makePostRequest(ctx, p)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// makePutRequest shim performs a PUT request, checking for a positive outcome.
// See simplehttp.MakePutRequest for more details.
func makePutRequest(ctx context.Context, r *httpRequest, p payload) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makePutRequest(ctx, p)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// makeDeleteRequest shim performs a DELETE request, checking for a positive outcome.
// See simplehttp.MakeDeleteRequest for more details.
func makeDeleteRequest(ctx context.Context, r *httpRequest) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makeDeleteRequest(ctx)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// Extract the http status code from error object
func GetStatusFromErr(err error) int {
obj, ok := err.(*UnexpectedResponseError)
if !ok {
return -1
}
return obj.Actual
}

52
vendor/github.com/mailgun/mailgun-go/v4/rfc2822.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
package mailgun
import (
"strconv"
"strings"
"time"
)
// Mailgun uses RFC2822 format for timestamps everywhere ('Thu, 13 Oct 2011 18:02:00 GMT'), but
// by default Go's JSON package uses another format when decoding/encoding timestamps.
type RFC2822Time time.Time
func NewRFC2822Time(str string) (RFC2822Time, error) {
t, err := time.Parse(time.RFC1123, str)
if err != nil {
return RFC2822Time{}, err
}
return RFC2822Time(t), nil
}
func (t RFC2822Time) Unix() int64 {
return time.Time(t).Unix()
}
func (t RFC2822Time) IsZero() bool {
return time.Time(t).IsZero()
}
func (t RFC2822Time) MarshalJSON() ([]byte, error) {
return []byte(strconv.Quote(time.Time(t).Format(time.RFC1123))), nil
}
func (t *RFC2822Time) UnmarshalJSON(s []byte) error {
q, err := strconv.Unquote(string(s))
if err != nil {
return err
}
if *(*time.Time)(t), err = time.Parse(time.RFC1123, q); err != nil {
if strings.Contains(err.Error(), "extra text") {
if *(*time.Time)(t), err = time.Parse(time.RFC1123Z, q); err != nil {
return err
}
return nil
}
return err
}
return nil
}
func (t RFC2822Time) String() string {
return time.Time(t).Format(time.RFC1123)
}

272
vendor/github.com/mailgun/mailgun-go/v4/routes.go generated vendored Normal file
View File

@@ -0,0 +1,272 @@
package mailgun
import (
"context"
"strconv"
)
// A Route structure contains information on a configured or to-be-configured route.
// When creating a new route, the SDK only uses a subset of the fields of this structure.
// In particular, CreatedAt and ID are meaningless in this context, and will be ignored.
// Only Priority, Description, Expression, and Actions need be provided.
type Route struct {
// The Priority field indicates how soon the route works relative to other configured routes.
// Routes of equal priority are consulted in chronological order.
Priority int `json:"priority,omitempty"`
// The Description field provides a human-readable description for the route.
// Mailgun ignores this field except to provide the description when viewing the Mailgun web control panel.
Description string `json:"description,omitempty"`
// The Expression field lets you specify a pattern to match incoming messages against.
Expression string `json:"expression,omitempty"`
// The Actions field contains strings specifying what to do
// with any message which matches the provided expression.
Actions []string `json:"actions,omitempty"`
// The CreatedAt field provides a time-stamp for when the route came into existence.
CreatedAt RFC2822Time `json:"created_at,omitempty"`
// ID field provides a unique identifier for this route.
Id string `json:"id,omitempty"`
}
type routesListResponse struct {
// is -1 if Next() or First() have not been called
TotalCount int `json:"total_count"`
Items []Route `json:"items"`
}
type createRouteResp struct {
Message string `json:"message"`
Route `json:"route"`
}
// ListRoutes allows you to iterate through a list of routes returned by the API
func (mg *MailgunImpl) ListRoutes(opts *ListOptions) *RoutesIterator {
var limit int
if opts != nil {
limit = opts.Limit
}
if limit == 0 {
limit = 100
}
return &RoutesIterator{
mg: mg,
url: generatePublicApiUrl(mg, routesEndpoint),
routesListResponse: routesListResponse{TotalCount: -1},
limit: limit,
}
}
type RoutesIterator struct {
routesListResponse
limit int
mg Mailgun
offset int
url string
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ri *RoutesIterator) Err() error {
return ri.err
}
// Offset returns the current offset of the iterator
func (ri *RoutesIterator) Offset() int {
return ri.offset
}
// 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 (ri *RoutesIterator) Next(ctx context.Context, items *[]Route) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Route, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
ri.offset = ri.offset + len(ri.Items)
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 (ri *RoutesIterator) First(ctx context.Context, items *[]Route) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, 0, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Route, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
ri.offset = len(ri.Items)
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 (ri *RoutesIterator) Last(ctx context.Context, items *[]Route) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.TotalCount - ri.limit
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Route, len(ri.Items))
copy(cpy, ri.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 (ri *RoutesIterator) Previous(ctx context.Context, items *[]Route) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.offset - (ri.limit * 2)
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Route, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
return true
}
func (ri *RoutesIterator) fetch(ctx context.Context, skip, limit int) error {
r := newHTTPRequest(ri.url)
r.setBasicAuth(basicAuthUser, ri.mg.APIKey())
r.setClient(ri.mg.Client())
if skip != 0 {
r.addParameter("skip", strconv.Itoa(skip))
}
if limit != 0 {
r.addParameter("limit", strconv.Itoa(limit))
}
return getResponseFromJSON(ctx, r, &ri.routesListResponse)
}
// CreateRoute installs a new route for your domain.
// The route structure you provide serves as a template, and
// only a subset of the fields influence the operation.
// See the Route structure definition for more details.
func (mg *MailgunImpl) CreateRoute(ctx context.Context, prototype Route) (_ignored Route, err error) {
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("priority", strconv.Itoa(prototype.Priority))
p.addValue("description", prototype.Description)
p.addValue("expression", prototype.Expression)
for _, action := range prototype.Actions {
p.addValue("action", action)
}
var resp createRouteResp
if err = postResponseFromJSON(ctx, r, p, &resp); err != nil {
return _ignored, err
}
return resp.Route, err
}
// DeleteRoute removes the specified route from your domain's configuration.
// To avoid ambiguity, Mailgun identifies the route by unique ID.
// See the Route structure definition and the Mailgun API documentation for more details.
func (mg *MailgunImpl) DeleteRoute(ctx context.Context, id string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint) + "/" + id)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetRoute retrieves the complete route definition associated with the unique route ID.
func (mg *MailgunImpl) GetRoute(ctx context.Context, id string) (Route, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint) + "/" + id)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var envelope struct {
Message string `json:"message"`
*Route `json:"route"`
}
err := getResponseFromJSON(ctx, r, &envelope)
if err != nil {
return Route{}, err
}
return *envelope.Route, err
}
// UpdateRoute provides an "in-place" update of the specified route.
// Only those route fields which are non-zero or non-empty are updated.
// All other fields remain as-is.
func (mg *MailgunImpl) UpdateRoute(ctx context.Context, id string, route Route) (Route, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint) + "/" + id)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if route.Priority != 0 {
p.addValue("priority", strconv.Itoa(route.Priority))
}
if route.Description != "" {
p.addValue("description", route.Description)
}
if route.Expression != "" {
p.addValue("expression", route.Expression)
}
if route.Actions != nil {
for _, action := range route.Actions {
p.addValue("action", action)
}
}
// For some reason, this API function just returns a bare Route on success.
// Unsure why this is the case; it seems like it ought to be a bug.
var envelope Route
err := putResponseFromJSON(ctx, r, p, &envelope)
return envelope, err
}

View File

@@ -0,0 +1,174 @@
package mailgun
import (
"context"
"strconv"
)
const (
complaintsEndpoint = "complaints"
)
// Complaint structures track how many times one of your emails have been marked as spam.
// the recipient thought your messages were not solicited.
type Complaint struct {
Count int `json:"count"`
CreatedAt RFC2822Time `json:"created_at"`
Address string `json:"address"`
}
type complaintsResponse struct {
Paging Paging `json:"paging"`
Items []Complaint `json:"items"`
}
// ListComplaints returns a set of spam complaints registered against your domain.
// Recipients of your messages can click on a link which sends feedback to Mailgun
// indicating that the message they received is, to them, spam.
func (mg *MailgunImpl) ListComplaints(opts *ListOptions) *ComplaintsIterator {
r := newHTTPRequest(generateApiUrl(mg, complaintsEndpoint))
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 &ComplaintsIterator{
mg: mg,
complaintsResponse: complaintsResponse{Paging: Paging{Next: url, First: url}},
err: err,
}
}
type ComplaintsIterator struct {
complaintsResponse
mg Mailgun
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ci *ComplaintsIterator) 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 *ComplaintsIterator) Next(ctx context.Context, items *[]Complaint) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Next)
if ci.err != nil {
return false
}
cpy := make([]Complaint, 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 *ComplaintsIterator) First(ctx context.Context, items *[]Complaint) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.First)
if ci.err != nil {
return false
}
cpy := make([]Complaint, 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 *ComplaintsIterator) Last(ctx context.Context, items *[]Complaint) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Last)
if ci.err != nil {
return false
}
cpy := make([]Complaint, 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 *ComplaintsIterator) Previous(ctx context.Context, items *[]Complaint) 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([]Complaint, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
if len(ci.Items) == 0 {
return false
}
return true
}
func (ci *ComplaintsIterator) 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.complaintsResponse)
}
// GetComplaint returns a single complaint record filed by a recipient at the email address provided.
// If no complaint exists, the Complaint instance returned will be empty.
func (mg *MailgunImpl) GetComplaint(ctx context.Context, address string) (Complaint, error) {
r := newHTTPRequest(generateApiUrl(mg, complaintsEndpoint) + "/" + address)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var c Complaint
err := getResponseFromJSON(ctx, r, &c)
return c, err
}
// CreateComplaint registers the specified address as a recipient who has complained of receiving spam
// from your domain.
func (mg *MailgunImpl) CreateComplaint(ctx context.Context, address string) error {
r := newHTTPRequest(generateApiUrl(mg, complaintsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("address", address)
_, err := makePostRequest(ctx, r, p)
return err
}
// DeleteComplaint removes a previously registered e-mail address from the list of people who complained
// of receiving spam from your domain.
func (mg *MailgunImpl) DeleteComplaint(ctx context.Context, address string) error {
r := newHTTPRequest(generateApiUrl(mg, complaintsEndpoint) + "/" + address)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}

120
vendor/github.com/mailgun/mailgun-go/v4/stats.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
package mailgun
import (
"context"
"strconv"
"time"
)
// Stats on accepted messages
type Accepted struct {
Incoming int `json:"incoming"`
Outgoing int `json:"outgoing"`
Total int `json:"total"`
}
// Stats on delivered messages
type Delivered struct {
Smtp int `json:"smtp"`
Http int `json:"http"`
Total int `json:"total"`
}
// Stats on temporary failures
type Temporary struct {
Espblock int `json:"espblock"`
}
// Stats on permanent failures
type Permanent struct {
SuppressBounce int `json:"suppress-bounce"`
SuppressUnsubscribe int `json:"suppress-unsubscribe"`
SuppressComplaint int `json:"suppress-complaint"`
Bounce int `json:"bounce"`
DelayedBounce int `json:"delayed-bounce"`
Total int `json:"total"`
}
// Stats on failed messages
type Failed struct {
Temporary Temporary `json:"temporary"`
Permanent Permanent `json:"permanent"`
}
// Total stats for messages
type Total struct {
Total int `json:"total"`
}
// Stats as returned by `GetStats()`
type Stats struct {
Time string `json:"time"`
Accepted Accepted `json:"accepted"`
Delivered Delivered `json:"delivered"`
Failed Failed `json:"failed"`
Stored Total `json:"stored"`
Opened Total `json:"opened"`
Clicked Total `json:"clicked"`
Unsubscribed Total `json:"unsubscribed"`
Complained Total `json:"complained"`
}
type statsTotalResponse struct {
End string `json:"end"`
Resolution string `json:"resolution"`
Start string `json:"start"`
Stats []Stats `json:"stats"`
}
// Used by GetStats() to specify the resolution stats are for
type Resolution string
// Indicate which resolution a stat response for request is for
const (
ResolutionHour = Resolution("hour")
ResolutionDay = Resolution("day")
ResolutionMonth = Resolution("month")
)
// Options for GetStats()
type GetStatOptions struct {
Resolution Resolution
Duration string
Start time.Time
End time.Time
}
// GetStats returns total stats for a given domain for the specified time period
func (mg *MailgunImpl) GetStats(ctx context.Context, events []string, opts *GetStatOptions) ([]Stats, error) {
r := newHTTPRequest(generateApiUrl(mg, statsTotalEndpoint))
if opts != nil {
if !opts.Start.IsZero() {
r.addParameter("start", strconv.Itoa(int(opts.Start.Unix())))
}
if !opts.End.IsZero() {
r.addParameter("end", strconv.Itoa(int(opts.End.Unix())))
}
if opts.Resolution != "" {
r.addParameter("resolution", string(opts.Resolution))
}
if opts.Duration != "" {
r.addParameter("duration", opts.Duration)
}
}
for _, e := range events {
r.addParameter("event", e)
}
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var res statsTotalResponse
err := getResponseFromJSON(ctx, r, &res)
if err != nil {
return nil, err
} else {
return res.Stats, nil
}
}

183
vendor/github.com/mailgun/mailgun-go/v4/tags.go generated vendored Normal file
View File

@@ -0,0 +1,183 @@
package mailgun
import (
"context"
"net/url"
"strconv"
"time"
)
type Tag struct {
Value string `json:"tag"`
Description string `json:"description"`
FirstSeen *time.Time `json:"first-seen,omitempty"`
LastSeen *time.Time `json:"last-seen,omitempty"`
}
type tagsResponse struct {
Items []Tag `json:"items"`
Paging Paging `json:"paging"`
}
type ListTagOptions struct {
// Restrict the page size to this limit
Limit int
// Return only the tags starting with the given prefix
Prefix string
}
// DeleteTag removes all counters for a particular tag, including the tag itself.
func (mg *MailgunImpl) DeleteTag(ctx context.Context, tag string) error {
r := newHTTPRequest(generateApiUrl(mg, tagsEndpoint) + "/" + tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetTag retrieves metadata about the tag from the api
func (mg *MailgunImpl) GetTag(ctx context.Context, tag string) (Tag, error) {
r := newHTTPRequest(generateApiUrl(mg, tagsEndpoint) + "/" + tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var tagItem Tag
return tagItem, getResponseFromJSON(ctx, r, &tagItem)
}
// ListTags returns a cursor used to iterate through a list of tags
// it := mg.ListTags(nil)
// var page []mailgun.Tag
// for it.Next(&page) {
// for _, tag := range(page) {
// // Do stuff with tags
// }
// }
// if it.Err() != nil {
// log.Fatal(it.Err())
// }
func (mg *MailgunImpl) ListTags(opts *ListTagOptions) *TagIterator {
req := newHTTPRequest(generateApiUrl(mg, tagsEndpoint))
if opts != nil {
if opts.Limit != 0 {
req.addParameter("limit", strconv.Itoa(opts.Limit))
}
if opts.Prefix != "" {
req.addParameter("prefix", opts.Prefix)
}
}
url, err := req.generateUrlWithParameters()
return &TagIterator{
tagsResponse: tagsResponse{Paging: Paging{Next: url, First: url}},
err: err,
mg: mg,
}
}
type TagIterator struct {
tagsResponse
mg Mailgun
err error
}
// Next returns the next page in the list of tags
func (ti *TagIterator) Next(ctx context.Context, items *[]Tag) bool {
if ti.err != nil {
return false
}
if !canFetchPage(ti.Paging.Next) {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Next)
if ti.err != nil {
return false
}
*items = ti.Items
if len(ti.Items) == 0 {
return false
}
return true
}
// Previous returns the previous page in the list of tags
func (ti *TagIterator) Previous(ctx context.Context, items *[]Tag) bool {
if ti.err != nil {
return false
}
if ti.Paging.Previous == "" {
return false
}
if !canFetchPage(ti.Paging.Previous) {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Previous)
if ti.err != nil {
return false
}
*items = ti.Items
if len(ti.Items) == 0 {
return false
}
return true
}
// First returns the first page in the list of tags
func (ti *TagIterator) First(ctx context.Context, items *[]Tag) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.First)
if ti.err != nil {
return false
}
*items = ti.Items
return true
}
// Last returns the last page in the list of tags
func (ti *TagIterator) Last(ctx context.Context, items *[]Tag) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Last)
if ti.err != nil {
return false
}
*items = ti.Items
return true
}
// Err returns any error if one occurred
func (ti *TagIterator) Err() error {
return ti.err
}
func (ti *TagIterator) fetch(ctx context.Context, url string) error {
req := newHTTPRequest(url)
req.setClient(ti.mg.Client())
req.setBasicAuth(basicAuthUser, ti.mg.APIKey())
return getResponseFromJSON(ctx, req, &ti.tagsResponse)
}
func canFetchPage(slug string) bool {
parts, err := url.Parse(slug)
if err != nil {
return false
}
params, _ := url.ParseQuery(parts.RawQuery)
if err != nil {
return false
}
value, ok := params["tag"]
// If tags doesn't exist, it's our first time fetching pages
if !ok {
return true
}
// If tags has no value, there are no more pages to fetch
return len(value) == 0
}

243
vendor/github.com/mailgun/mailgun-go/v4/template.go generated vendored Normal file
View File

@@ -0,0 +1,243 @@
package mailgun
import (
"context"
"errors"
"strconv"
)
type TemplateEngine string
// Used by CreateTemplate() and AddTemplateVersion() to specify the template engine
const (
TemplateEngineMustache = TemplateEngine("mustache")
TemplateEngineHandlebars = TemplateEngine("handlebars")
TemplateEngineGo = TemplateEngine("go")
)
type Template struct {
Name string `json:"name"`
Description string `json:"description"`
CreatedAt RFC2822Time `json:"createdAt"`
Version TemplateVersion `json:"version,omitempty"`
}
type templateResp struct {
Item Template `json:"template"`
Message string `json:"message"`
}
type templateListResp struct {
Items []Template `json:"items"`
Paging Paging `json:"paging"`
}
// Create a new template which can be used to attach template versions to
func (mg *MailgunImpl) CreateTemplate(ctx context.Context, template *Template) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
if template.Name != "" {
payload.addValue("name", template.Name)
}
if template.Description != "" {
payload.addValue("description", template.Description)
}
if template.Version.Engine != "" {
payload.addValue("engine", string(template.Version.Engine))
}
if template.Version.Template != "" {
payload.addValue("template", template.Version.Template)
}
if template.Version.Comment != "" {
payload.addValue("comment", template.Version.Comment)
}
if template.Version.Tag != "" {
payload.addValue("tag", template.Version.Tag)
}
var resp templateResp
if err := postResponseFromJSON(ctx, r, payload, &resp); err != nil {
return err
}
*template = resp.Item
return nil
}
// GetTemplate gets a template given the template name
func (mg *MailgunImpl) GetTemplate(ctx context.Context, name string) (Template, error) {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + name)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addParameter("active", "yes")
var resp templateResp
err := getResponseFromJSON(ctx, r, &resp)
if err != nil {
return Template{}, err
}
return resp.Item, nil
}
// Update the name and description of a template
func (mg *MailgunImpl) UpdateTemplate(ctx context.Context, template *Template) error {
if template.Name == "" {
return errors.New("UpdateTemplate() Template.Name cannot be empty")
}
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + template.Name)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if template.Name != "" {
p.addValue("name", template.Name)
}
if template.Description != "" {
p.addValue("description", template.Description)
}
var resp templateResp
err := putResponseFromJSON(ctx, r, p, &resp)
if err != nil {
return err
}
*template = resp.Item
return nil
}
// Delete a template given a template name
func (mg *MailgunImpl) DeleteTemplate(ctx context.Context, name string) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + name)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
type TemplatesIterator struct {
templateListResp
mg Mailgun
err error
}
type ListTemplateOptions struct {
Limit int
Active bool
}
// List all available templates
func (mg *MailgunImpl) ListTemplates(opts *ListTemplateOptions) *TemplatesIterator {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
if opts != nil {
if opts.Limit != 0 {
r.addParameter("limit", strconv.Itoa(opts.Limit))
}
if opts.Active {
r.addParameter("active", "yes")
}
}
url, err := r.generateUrlWithParameters()
return &TemplatesIterator{
mg: mg,
templateListResp: templateListResp{Paging: Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (ti *TemplatesIterator) Err() error {
return ti.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 (ti *TemplatesIterator) Next(ctx context.Context, items *[]Template) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Next)
if ti.err != nil {
return false
}
cpy := make([]Template, len(ti.Items))
copy(cpy, ti.Items)
*items = cpy
if len(ti.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 (ti *TemplatesIterator) First(ctx context.Context, items *[]Template) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.First)
if ti.err != nil {
return false
}
cpy := make([]Template, len(ti.Items))
copy(cpy, ti.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 (ti *TemplatesIterator) Last(ctx context.Context, items *[]Template) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Last)
if ti.err != nil {
return false
}
cpy := make([]Template, len(ti.Items))
copy(cpy, ti.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 (ti *TemplatesIterator) Previous(ctx context.Context, items *[]Template) bool {
if ti.err != nil {
return false
}
if ti.Paging.Previous == "" {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Previous)
if ti.err != nil {
return false
}
cpy := make([]Template, len(ti.Items))
copy(cpy, ti.Items)
*items = cpy
if len(ti.Items) == 0 {
return false
}
return true
}
func (ti *TemplatesIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(ti.mg.Client())
r.setBasicAuth(basicAuthUser, ti.mg.APIKey())
return getResponseFromJSON(ctx, r, &ti.templateListResp)
}

View File

@@ -0,0 +1,217 @@
package mailgun
import (
"context"
"strconv"
)
type TemplateVersion struct {
Tag string `json:"tag"`
Template string `json:"template,omitempty"`
Engine TemplateEngine `json:"engine"`
CreatedAt RFC2822Time `json:"createdAt"`
Comment string `json:"comment"`
Active bool `json:"active"`
}
type templateVersionListResp struct {
Template struct {
Template
Versions []TemplateVersion `json:"versions,omitempty"`
} `json:"template"`
Paging Paging `json:"paging"`
}
// AddTemplateVersion adds a template version to a template
func (mg *MailgunImpl) AddTemplateVersion(ctx context.Context, templateName string, version *TemplateVersion) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("template", version.Template)
if version.Tag != "" {
payload.addValue("tag", string(version.Tag))
}
if version.Engine != "" {
payload.addValue("engine", string(version.Engine))
}
if version.Comment != "" {
payload.addValue("comment", version.Comment)
}
if version.Active {
payload.addValue("active", boolToString(version.Active))
}
var resp templateResp
if err := postResponseFromJSON(ctx, r, payload, &resp); err != nil {
return err
}
*version = resp.Item.Version
return nil
}
// GetTemplateVersion gets a specific version of a template
func (mg *MailgunImpl) GetTemplateVersion(ctx context.Context, templateName, tag string) (TemplateVersion, error) {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions/" + tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp templateResp
err := getResponseFromJSON(ctx, r, &resp)
if err != nil {
return TemplateVersion{}, err
}
return resp.Item.Version, nil
}
// Update the comment and mark a version of a template active
func (mg *MailgunImpl) UpdateTemplateVersion(ctx context.Context, templateName string, version *TemplateVersion) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions/" + version.Tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if version.Comment != "" {
p.addValue("comment", version.Comment)
}
if version.Active {
p.addValue("active", boolToString(version.Active))
}
if version.Template != "" {
p.addValue("template", version.Template)
}
var resp templateResp
err := putResponseFromJSON(ctx, r, p, &resp)
if err != nil {
return err
}
*version = resp.Item.Version
return nil
}
// Delete a specific version of a template
func (mg *MailgunImpl) DeleteTemplateVersion(ctx context.Context, templateName, tag string) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions/" + tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
type TemplateVersionsIterator struct {
templateVersionListResp
mg Mailgun
err error
}
// List all the versions of a specific template
func (mg *MailgunImpl) ListTemplateVersions(templateName string, opts *ListOptions) *TemplateVersionsIterator {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions")
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 &TemplateVersionsIterator{
mg: mg,
templateVersionListResp: templateVersionListResp{Paging: Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (li *TemplateVersionsIterator) Err() error {
return li.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 (li *TemplateVersionsIterator) Next(ctx context.Context, items *[]TemplateVersion) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Next)
if li.err != nil {
return false
}
cpy := make([]TemplateVersion, len(li.Template.Versions))
copy(cpy, li.Template.Versions)
*items = cpy
if len(li.Template.Versions) == 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 (li *TemplateVersionsIterator) First(ctx context.Context, items *[]TemplateVersion) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.First)
if li.err != nil {
return false
}
cpy := make([]TemplateVersion, len(li.Template.Versions))
copy(cpy, li.Template.Versions)
*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 (li *TemplateVersionsIterator) Last(ctx context.Context, items *[]TemplateVersion) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Last)
if li.err != nil {
return false
}
cpy := make([]TemplateVersion, len(li.Template.Versions))
copy(cpy, li.Template.Versions)
*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 (li *TemplateVersionsIterator) Previous(ctx context.Context, items *[]TemplateVersion) bool {
if li.err != nil {
return false
}
if li.Paging.Previous == "" {
return false
}
li.err = li.fetch(ctx, li.Paging.Previous)
if li.err != nil {
return false
}
cpy := make([]TemplateVersion, len(li.Template.Versions))
copy(cpy, li.Template.Versions)
*items = cpy
if len(li.Template.Versions) == 0 {
return false
}
return true
}
func (li *TemplateVersionsIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(li.mg.Client())
r.setBasicAuth(basicAuthUser, li.mg.APIKey())
return getResponseFromJSON(ctx, r, &li.templateVersionListResp)
}

180
vendor/github.com/mailgun/mailgun-go/v4/unsubscribes.go generated vendored Normal file
View File

@@ -0,0 +1,180 @@
package mailgun
import (
"context"
"strconv"
)
type Unsubscribe struct {
CreatedAt RFC2822Time `json:"created_at"`
Tags []string `json:"tags"`
ID string `json:"id"`
Address string `json:"address"`
}
type unsubscribesResponse struct {
Paging Paging `json:"paging"`
Items []Unsubscribe `json:"items"`
}
// Fetches the list of unsubscribes
func (mg *MailgunImpl) ListUnsubscribes(opts *ListOptions) *UnsubscribesIterator {
r := newHTTPRequest(generateApiUrl(mg, unsubscribesEndpoint))
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 &UnsubscribesIterator{
mg: mg,
unsubscribesResponse: unsubscribesResponse{Paging: Paging{Next: url, First: url}},
err: err,
}
}
type UnsubscribesIterator struct {
unsubscribesResponse
mg Mailgun
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ci *UnsubscribesIterator) 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 *UnsubscribesIterator) Next(ctx context.Context, items *[]Unsubscribe) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Next)
if ci.err != nil {
return false
}
cpy := make([]Unsubscribe, 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 *UnsubscribesIterator) First(ctx context.Context, items *[]Unsubscribe) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.First)
if ci.err != nil {
return false
}
cpy := make([]Unsubscribe, 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 *UnsubscribesIterator) Last(ctx context.Context, items *[]Unsubscribe) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Last)
if ci.err != nil {
return false
}
cpy := make([]Unsubscribe, 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 *UnsubscribesIterator) Previous(ctx context.Context, items *[]Unsubscribe) 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([]Unsubscribe, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
if len(ci.Items) == 0 {
return false
}
return true
}
func (ci *UnsubscribesIterator) 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.unsubscribesResponse)
}
// Retreives a single unsubscribe record. Can be used to check if a given address is present in the list of unsubscribed users.
func (mg *MailgunImpl) GetUnsubscribe(ctx context.Context, address string) (Unsubscribe, error) {
r := newHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, address))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
envelope := Unsubscribe{}
err := getResponseFromJSON(ctx, r, &envelope)
return envelope, err
}
// Unsubscribe adds an e-mail address to the domain's unsubscription table.
func (mg *MailgunImpl) CreateUnsubscribe(ctx context.Context, address, tag string) error {
r := newHTTPRequest(generateApiUrl(mg, unsubscribesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("address", address)
p.addValue("tag", tag)
_, err := makePostRequest(ctx, r, p)
return err
}
// DeleteUnsubscribe removes the e-mail address given from the domain's unsubscription table.
// If passing in an ID (discoverable from, e.g., ListUnsubscribes()), the e-mail address associated
// with the given ID will be removed.
func (mg *MailgunImpl) DeleteUnsubscribe(ctx context.Context, address string) error {
r := newHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, address))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// DeleteUnsubscribeWithTag removes the e-mail address given from the domain's unsubscription table with a matching tag.
// If passing in an ID (discoverable from, e.g., ListUnsubscribes()), the e-mail address associated
// with the given ID will be removed.
func (mg *MailgunImpl) DeleteUnsubscribeWithTag(ctx context.Context, a, t string) error {
r := newHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, a))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addParameter("tag", t)
_, err := makeDeleteRequest(ctx, r)
return err
}

4
vendor/github.com/mailgun/mailgun-go/v4/version.go generated vendored Normal file
View File

@@ -0,0 +1,4 @@
package mailgun
// Version of current release
const Version = "4.2.0"

157
vendor/github.com/mailgun/mailgun-go/v4/webhooks.go generated vendored Normal file
View File

@@ -0,0 +1,157 @@
package mailgun
import (
"context"
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"fmt"
"io"
"net/http"
"github.com/mailgun/mailgun-go/v4/events"
)
type UrlOrUrls struct {
Urls []string `json:"urls"`
Url string `json:"url"`
}
type WebHooksListResponse struct {
Webhooks map[string]UrlOrUrls `json:"webhooks"`
}
type WebHookResponse struct {
Webhook UrlOrUrls `json:"webhook"`
}
// ListWebhooks returns the complete set of webhooks configured for your domain.
// Note that a zero-length mapping is not an error.
func (mg *MailgunImpl) ListWebhooks(ctx context.Context) (map[string][]string, error) {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var body WebHooksListResponse
err := getResponseFromJSON(ctx, r, &body)
if err != nil {
return nil, err
}
hooks := make(map[string][]string, 0)
for k, v := range body.Webhooks {
if v.Url != "" {
hooks[k] = []string{v.Url}
}
if len(v.Urls) != 0 {
hooks[k] = append(hooks[k], v.Urls...)
}
}
return hooks, nil
}
// CreateWebhook installs a new webhook for your domain.
func (mg *MailgunImpl) CreateWebhook(ctx context.Context, kind string, urls []string) error {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("id", kind)
for _, url := range urls {
p.addValue("url", url)
}
_, err := makePostRequest(ctx, r, p)
return err
}
// DeleteWebhook removes the specified webhook from your domain's configuration.
func (mg *MailgunImpl) DeleteWebhook(ctx context.Context, kind string) error {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetWebhook retrieves the currently assigned webhook URL associated with the provided type of webhook.
func (mg *MailgunImpl) GetWebhook(ctx context.Context, kind string) ([]string, error) {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var body WebHookResponse
if err := getResponseFromJSON(ctx, r, &body); err != nil {
return nil, err
}
if body.Webhook.Url != "" {
return []string{body.Webhook.Url}, nil
}
if len(body.Webhook.Urls) != 0 {
return body.Webhook.Urls, nil
}
return nil, fmt.Errorf("webhook '%s' returned no urls", kind)
}
// UpdateWebhook replaces one webhook setting for another.
func (mg *MailgunImpl) UpdateWebhook(ctx context.Context, kind string, urls []string) error {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
for _, url := range urls {
p.addValue("url", url)
}
_, err := makePutRequest(ctx, r, p)
return err
}
// Represents the signature portion of the webhook POST body
type Signature struct {
TimeStamp string `json:"timestamp"`
Token string `json:"token"`
Signature string `json:"signature"`
}
// Represents the JSON payload provided when a Webhook is called by mailgun
type WebhookPayload struct {
Signature Signature `json:"signature"`
EventData events.RawJSON `json:"event-data"`
}
// Use this method to parse the webhook signature given as JSON in the webhook response
func (mg *MailgunImpl) VerifyWebhookSignature(sig Signature) (verified bool, err error) {
h := hmac.New(sha256.New, []byte(mg.APIKey()))
io.WriteString(h, sig.TimeStamp)
io.WriteString(h, sig.Token)
calculatedSignature := h.Sum(nil)
signature, err := hex.DecodeString(sig.Signature)
if err != nil {
return false, err
}
if len(calculatedSignature) != len(signature) {
return false, nil
}
return subtle.ConstantTimeCompare(signature, calculatedSignature) == 1, nil
}
// Deprecated: Please use the VerifyWebhookSignature() to parse the latest
// version of WebHooks from mailgun
func (mg *MailgunImpl) VerifyWebhookRequest(req *http.Request) (verified bool, err error) {
h := hmac.New(sha256.New, []byte(mg.APIKey()))
io.WriteString(h, req.FormValue("timestamp"))
io.WriteString(h, req.FormValue("token"))
calculatedSignature := h.Sum(nil)
signature, err := hex.DecodeString(req.FormValue("signature"))
if err != nil {
return false, err
}
if len(calculatedSignature) != len(signature) {
return false, nil
}
return subtle.ConstantTimeCompare(signature, calculatedSignature) == 1, nil
}