replace zxq.co/ripple/hanayo
This commit is contained in:
2
vendor/gopkg.in/mailgun/mailgun-go.v1/.gitignore
generated
vendored
Normal file
2
vendor/gopkg.in/mailgun/mailgun-go.v1/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
.idea/
|
14
vendor/gopkg.in/mailgun/mailgun-go.v1/.travis.yml
generated
vendored
Normal file
14
vendor/gopkg.in/mailgun/mailgun-go.v1/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
|
||||
sudo: true
|
||||
|
||||
before_script:
|
||||
- sudo apt-get install haveged && sudo service haveged start
|
||||
|
||||
script:
|
||||
- make
|
27
vendor/gopkg.in/mailgun/mailgun-go.v1/LICENSE
generated
vendored
Normal file
27
vendor/gopkg.in/mailgun/mailgun-go.v1/LICENSE
generated
vendored
Normal 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.
|
9
vendor/gopkg.in/mailgun/mailgun-go.v1/Makefile
generated
vendored
Normal file
9
vendor/gopkg.in/mailgun/mailgun-go.v1/Makefile
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
.PHONY: all
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
all:
|
||||
# Only run these tests if secure credentials exist
|
||||
ifeq ($(TRAVIS_SECURE_ENV_VARS),true)
|
||||
go get -t .
|
||||
go test .
|
||||
endif
|
69
vendor/gopkg.in/mailgun/mailgun-go.v1/README.md
generated
vendored
Normal file
69
vendor/gopkg.in/mailgun/mailgun-go.v1/README.md
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
Mailgun with Go
|
||||
===============
|
||||
|
||||
[](https://travis-ci.org/mailgun/mailgun-go)
|
||||
[](https://godoc.org/gopkg.in/mailgun/mailgun-go.v1)
|
||||
|
||||
|
||||
Go library for interacting with the [Mailgun](https://mailgun.com/) [API](https://documentation.mailgun.com/api_reference.html).
|
||||
|
||||
# Sending mail via the mailgun CLI
|
||||
Export your API keys and domain
|
||||
```bash
|
||||
$ export MG_API_KEY=your-api-key
|
||||
$ export MG_DOMAIN=your-domain
|
||||
$ export MG_PUBLIC_API_KEY=your-public-key
|
||||
$ export MG_URL="https://api.mailgun.net/v3"
|
||||
```
|
||||
Send an email
|
||||
```bash
|
||||
$ echo -n 'Hello World' | mailgun send -s "Test subject" address@example.com
|
||||
```
|
||||
|
||||
# Sending mail via the golang library
|
||||
```go
|
||||
package main
|
||||
|
||||
import "gopkg.in/mailgun/mailgun-go.v1"
|
||||
|
||||
mg := mailgun.NewMailgun(yourdomain, ApiKey, publicApiKey)
|
||||
message := mailgun.NewMessage(
|
||||
"sender@example.com",
|
||||
"Fancy subject!",
|
||||
"Hello from Mailgun Go!",
|
||||
"recipient@example.com")
|
||||
resp, id, err := mg.Send(message)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("ID: %s Resp: %s\n", id, resp)
|
||||
```
|
||||
|
||||
# Installation
|
||||
Install the go library
|
||||
```
|
||||
go get gopkg.in/mailgun/mailgun-go.v1
|
||||
```
|
||||
|
||||
Install the mailgun CLI
|
||||
```
|
||||
go install github.com/mailgun/mailgun-go/cmd/mailgun/./...
|
||||
```
|
||||
|
||||
# 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 API key - you can get this value from the Mailgun admin interface.
|
||||
* `MG_API_KEY` is the (private) API key - you can get this value from the Mailgun admin interface.
|
||||
* `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.
|
99
vendor/gopkg.in/mailgun/mailgun-go.v1/acceptance_test.go
generated
vendored
Normal file
99
vendor/gopkg.in/mailgun/mailgun-go.v1/acceptance_test.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
"github.com/onsi/ginkgo"
|
||||
)
|
||||
|
||||
// Many tests require configuration settings unique to the user, passed in via
|
||||
// environment variables. If these variables aren't set, we need to fail the test early.
|
||||
func reqEnv(t ginkgo.GinkgoTInterface, variableName string) string {
|
||||
value := os.Getenv(variableName)
|
||||
ensure.True(t, value != "")
|
||||
return value
|
||||
}
|
||||
|
||||
func randomDomainURL(n int) string {
|
||||
const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
var bytes = make([]byte, n)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alpha[b%byte(len(alpha))]
|
||||
}
|
||||
return "http://" + string(bytes) + ".com"
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
func spendMoney(t *testing.T, tFunc func()) {
|
||||
ok := os.Getenv("MG_SPEND_MONEY")
|
||||
if ok != "" {
|
||||
tFunc()
|
||||
} else {
|
||||
t.Log("Money spending not allowed, not running function.")
|
||||
}
|
||||
}
|
||||
|
||||
func parseContentType(req *http.Request) (url.Values, error) {
|
||||
contentType, attrs, _ := mime.ParseMediaType(req.Header.Get("Content-Type"))
|
||||
if contentType != "multipart/form-data" {
|
||||
return nil, fmt.Errorf("unexpected content type: %v", contentType)
|
||||
}
|
||||
raw, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("err reading body of multipart request snapshot: %v", err)
|
||||
}
|
||||
boundary := attrs["boundary"]
|
||||
reader := multipart.NewReader(bytes.NewReader(raw), boundary)
|
||||
values := url.Values{}
|
||||
for {
|
||||
part, err := reader.NextPart()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("err getting nextpart: %v", err)
|
||||
}
|
||||
data, err := ioutil.ReadAll(part)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("err getting data from part: %v", err)
|
||||
}
|
||||
if bytes.Contains(data, []byte(boundary)) {
|
||||
return nil, fmt.Errorf("part contains the boundary, which is not expected")
|
||||
}
|
||||
values.Set(part.FormName(), string(data))
|
||||
if err = part.Close(); err != nil {
|
||||
return nil, fmt.Errorf("err closing part: %v", err)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
128
vendor/gopkg.in/mailgun/mailgun-go.v1/bounces.go
generated
vendored
Normal file
128
vendor/gopkg.in/mailgun/mailgun-go.v1/bounces.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Bounce aggregates data relating to undeliverable messages to a specific intended recipient,
|
||||
// identified by Address.
|
||||
// Code provides the SMTP error code causing the bounce,
|
||||
// while Error provides a human readable reason why.
|
||||
// CreatedAt provides the time at which Mailgun detected the bounce.
|
||||
type Bounce struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
code interface{} `json:"code"`
|
||||
Address string `json:"address"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type Paging struct {
|
||||
First string `json:"first"`
|
||||
Next string `json:"next"`
|
||||
Previous string `json:"previous"`
|
||||
Last string `json:"last"`
|
||||
}
|
||||
|
||||
type bounceEnvelope struct {
|
||||
Items []Bounce `json:"items"`
|
||||
Paging Paging `json:"paging"`
|
||||
}
|
||||
|
||||
// GetCreatedAt parses the textual, RFC-822 timestamp into a standard Go-compatible
|
||||
// Time structure.
|
||||
func (i Bounce) GetCreatedAt() (t time.Time, err error) {
|
||||
return parseMailgunTime(i.CreatedAt)
|
||||
}
|
||||
|
||||
// GetCode will return the bounce code for the message, regardless if it was
|
||||
// returned as a string or as an integer. This method overcomes a protocol
|
||||
// bug in the Mailgun API.
|
||||
func (b Bounce) GetCode() (int, error) {
|
||||
switch c := b.code.(type) {
|
||||
case int:
|
||||
return c, nil
|
||||
case string:
|
||||
return strconv.Atoi(c)
|
||||
default:
|
||||
return -1, strconv.ErrSyntax
|
||||
}
|
||||
}
|
||||
|
||||
// GetBounces returns a complete set of bounces logged against the sender's domain, if any.
|
||||
// The results include the total number of bounces (regardless of skip or limit settings),
|
||||
// and the slice of bounces specified, if successful.
|
||||
// Note that the length of the slice may be smaller than the total number of bounces.
|
||||
func (m *MailgunImpl) GetBounces(limit, skip int) (int, []Bounce, error) {
|
||||
r := newHTTPRequest(generateApiUrl(m, bouncesEndpoint))
|
||||
if limit != -1 {
|
||||
r.addParameter("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if skip != -1 {
|
||||
r.addParameter("skip", strconv.Itoa(skip))
|
||||
}
|
||||
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
var response bounceEnvelope
|
||||
err := getResponseFromJSON(r, &response)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
|
||||
return len(response.Items), response.Items, nil
|
||||
}
|
||||
|
||||
// GetSingleBounce retrieves a single bounce record, if any exist, for the given recipient address.
|
||||
func (m *MailgunImpl) GetSingleBounce(address string) (Bounce, error) {
|
||||
r := newHTTPRequest(generateApiUrl(m, bouncesEndpoint) + "/" + address)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
var response Bounce
|
||||
err := getResponseFromJSON(r, &response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// AddBounce files a bounce report.
|
||||
// Address identifies the intended recipient of the message that bounced.
|
||||
// Code corresponds to the numeric response given by the e-mail server which rejected the message.
|
||||
// Error providees the corresponding human readable reason for the problem.
|
||||
// For example,
|
||||
// here's how the these two fields relate.
|
||||
// Suppose the SMTP server responds with an error, as below.
|
||||
// Then, . . .
|
||||
//
|
||||
// 550 Requested action not taken: mailbox unavailable
|
||||
// \___/\_______________________________________________/
|
||||
// | |
|
||||
// `-- Code `-- Error
|
||||
//
|
||||
// Note that both code and error exist as strings, even though
|
||||
// code will report as a number.
|
||||
func (m *MailgunImpl) AddBounce(address, code, error string) error {
|
||||
r := newHTTPRequest(generateApiUrl(m, bouncesEndpoint))
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
payload := newUrlEncodedPayload()
|
||||
payload.addValue("address", address)
|
||||
if code != "" {
|
||||
payload.addValue("code", code)
|
||||
}
|
||||
if error != "" {
|
||||
payload.addValue("error", error)
|
||||
}
|
||||
_, err := makePostRequest(r, payload)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteBounce removes all bounces associted with the provided e-mail address.
|
||||
func (m *MailgunImpl) DeleteBounce(address string) error {
|
||||
r := newHTTPRequest(generateApiUrl(m, bouncesEndpoint) + "/" + address)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
103
vendor/gopkg.in/mailgun/mailgun-go.v1/bounces_test.go
generated
vendored
Normal file
103
vendor/gopkg.in/mailgun/mailgun-go.v1/bounces_test.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func TestGetBounces(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
n, bounces, err := mg.GetBounces(-1, -1)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, n, len(bounces))
|
||||
}
|
||||
|
||||
func TestGetSingleBounce(t *testing.T) {
|
||||
domain := reqEnv(t, "MG_DOMAIN")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
exampleEmail := fmt.Sprintf("%s@%s", strings.ToLower(randomString(64, "")), domain)
|
||||
_, err = mg.GetSingleBounce(exampleEmail)
|
||||
ensure.NotNil(t, err)
|
||||
|
||||
ure, ok := err.(*UnexpectedResponseError)
|
||||
ensure.True(t, ok)
|
||||
ensure.DeepEqual(t, ure.Actual, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestAddDelBounces(t *testing.T) {
|
||||
domain := reqEnv(t, "MG_DOMAIN")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
// Compute an e-mail address for our domain.
|
||||
exampleEmail := fmt.Sprintf("%s@%s", strings.ToLower(randomString(8, "bounce")), domain)
|
||||
|
||||
// First, basic sanity check.
|
||||
// Fail early if we have bounces for a fictitious e-mail address.
|
||||
|
||||
n, _, err := mg.GetBounces(-1, -1)
|
||||
ensure.Nil(t, err)
|
||||
// Add the bounce for our address.
|
||||
|
||||
err = mg.AddBounce(exampleEmail, "550", "TestAddDelBounces-generated error")
|
||||
ensure.Nil(t, err)
|
||||
|
||||
// We should now have one bounce listed when we query the API.
|
||||
|
||||
n, bounces, err := mg.GetBounces(-1, -1)
|
||||
ensure.Nil(t, err)
|
||||
if n == 0 {
|
||||
t.Fatal("Expected at least one bounce for this domain.")
|
||||
}
|
||||
|
||||
found := 0
|
||||
for _, bounce := range bounces {
|
||||
t.Logf("Bounce Address: %s\n", bounce.Address)
|
||||
if bounce.Address == exampleEmail {
|
||||
found++
|
||||
}
|
||||
}
|
||||
|
||||
if found == 0 {
|
||||
t.Fatalf("Expected bounce for address %s in list of bounces", exampleEmail)
|
||||
}
|
||||
|
||||
bounce, err := mg.GetSingleBounce(exampleEmail)
|
||||
ensure.Nil(t, err)
|
||||
if bounce.CreatedAt == "" {
|
||||
t.Fatalf("Expected at least one bounce for %s", exampleEmail)
|
||||
}
|
||||
|
||||
// Delete it. This should put us back the way we were.
|
||||
|
||||
err = mg.DeleteBounce(exampleEmail)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
// Make sure we're back to the way we were.
|
||||
|
||||
n, bounces, err = mg.GetBounces(-1, -1)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
found = 0
|
||||
for _, bounce := range bounces {
|
||||
t.Logf("Bounce Address: %s\n", bounce.Address)
|
||||
if bounce.Address == exampleEmail {
|
||||
found++
|
||||
}
|
||||
}
|
||||
|
||||
if found != 0 {
|
||||
t.Fatalf("Expected no bounce for address %s in list of bounces", exampleEmail)
|
||||
}
|
||||
|
||||
_, err = mg.GetSingleBounce(exampleEmail)
|
||||
ensure.NotNil(t, err)
|
||||
}
|
79
vendor/gopkg.in/mailgun/mailgun-go.v1/campaigns.go
generated
vendored
Normal file
79
vendor/gopkg.in/mailgun/mailgun-go.v1/campaigns.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package mailgun
|
||||
|
||||
// Campaigns have been deprecated since development work on this SDK commenced.
|
||||
// Please refer to http://documentation.mailgun.com/api_reference .
|
||||
type Campaign struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
DeliveredCount int `json:"delivered_count"`
|
||||
ClickedCount int `json:"clicked_count"`
|
||||
OpenedCount int `json:"opened_count"`
|
||||
SubmittedCount int `json:"submitted_count"`
|
||||
UnsubscribedCount int `json:"unsubscribed_count"`
|
||||
BouncedCount int `json:"bounced_count"`
|
||||
ComplainedCount int `json:"complained_count"`
|
||||
DroppedCount int `json:"dropped_count"`
|
||||
}
|
||||
|
||||
type campaignsEnvelope struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Items []Campaign `json:"items"`
|
||||
}
|
||||
|
||||
// Campaigns have been deprecated since development work on this SDK commenced.
|
||||
// Please refer to http://documentation.mailgun.com/api_reference .
|
||||
func (m *MailgunImpl) GetCampaigns() (int, []Campaign, error) {
|
||||
r := newHTTPRequest(generateApiUrl(m, campaignsEndpoint))
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
var envelope campaignsEnvelope
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
return envelope.TotalCount, envelope.Items, nil
|
||||
}
|
||||
|
||||
// Campaigns have been deprecated since development work on this SDK commenced.
|
||||
// Please refer to http://documentation.mailgun.com/api_reference .
|
||||
func (m *MailgunImpl) CreateCampaign(name, id string) error {
|
||||
r := newHTTPRequest(generateApiUrl(m, campaignsEndpoint))
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
payload := newUrlEncodedPayload()
|
||||
payload.addValue("name", name)
|
||||
if id != "" {
|
||||
payload.addValue("id", id)
|
||||
}
|
||||
_, err := makePostRequest(r, payload)
|
||||
return err
|
||||
}
|
||||
|
||||
// Campaigns have been deprecated since development work on this SDK commenced.
|
||||
// Please refer to http://documentation.mailgun.com/api_reference .
|
||||
func (m *MailgunImpl) UpdateCampaign(oldId, name, newId string) error {
|
||||
r := newHTTPRequest(generateApiUrl(m, campaignsEndpoint) + "/" + oldId)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
payload := newUrlEncodedPayload()
|
||||
payload.addValue("name", name)
|
||||
if newId != "" {
|
||||
payload.addValue("id", newId)
|
||||
}
|
||||
_, err := makePostRequest(r, payload)
|
||||
return err
|
||||
}
|
||||
|
||||
// Campaigns have been deprecated since development work on this SDK commenced.
|
||||
// Please refer to http://documentation.mailgun.com/api_reference .
|
||||
func (m *MailgunImpl) DeleteCampaign(id string) error {
|
||||
r := newHTTPRequest(generateApiUrl(m, campaignsEndpoint) + "/" + id)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
69
vendor/gopkg.in/mailgun/mailgun-go.v1/cmd/mailgun/main.go
generated
vendored
Normal file
69
vendor/gopkg.in/mailgun/mailgun-go.v1/cmd/mailgun/main.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mailgun/log"
|
||||
"github.com/mailgun/mailgun-go"
|
||||
"github.com/thrawn01/args"
|
||||
)
|
||||
|
||||
func checkErr(msg string, err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s - %s\n", msg, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.InitWithConfig(log.Config{Name: "console"})
|
||||
desc := args.Dedent(`CLI for mailgun api
|
||||
|
||||
Examples:
|
||||
Set your credentials in the environment
|
||||
export MG_DOMAIN=your-domain-name
|
||||
export MG_API_KEY=your-api-key
|
||||
export MG_PUBLIC_API_KEY=your-public-api-key
|
||||
|
||||
Post a simple event message from stdin
|
||||
$ echo -n 'Hello World' | mailgun send -s "Test subject" address@example.com
|
||||
|
||||
Help:
|
||||
For detailed help on send
|
||||
$ mailgun send -h`)
|
||||
|
||||
parser := args.NewParser(args.EnvPrefix("MG_"), args.Desc(desc, args.IsFormated))
|
||||
parser.AddOption("--verbose").Alias("-v").IsTrue().Help("be verbose")
|
||||
parser.AddOption("--url").Env("URL").Default(mailgun.ApiBase).Help("url to the mailgun api")
|
||||
parser.AddOption("--api-key").Env("API_KEY").Help("mailgun api key")
|
||||
parser.AddOption("--public-api-key").Env("PUBLIC_API_KEY").Help("mailgun public api key")
|
||||
parser.AddOption("--domain").Env("DOMAIN").Help("mailgun api key")
|
||||
|
||||
// Commands
|
||||
parser.AddCommand("send", Send)
|
||||
parser.AddCommand("tag", Tag)
|
||||
|
||||
// Parser and set global options
|
||||
opts := parser.ParseArgsSimple(nil)
|
||||
if opts.Bool("verbose") {
|
||||
mailgun.Debug = true
|
||||
}
|
||||
|
||||
// Initialize our mailgun object
|
||||
mg := mailgun.NewMailgun(
|
||||
opts.String("domain"),
|
||||
opts.String("api-key"),
|
||||
opts.String("public-api-key"))
|
||||
|
||||
// Set our api url
|
||||
mg.SetAPIBase(opts.String("url"))
|
||||
|
||||
// Run the command chosen by our user
|
||||
retCode, err := parser.RunCommand(mg)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(retCode)
|
||||
}
|
108
vendor/gopkg.in/mailgun/mailgun-go.v1/cmd/mailgun/send.go
generated
vendored
Normal file
108
vendor/gopkg.in/mailgun/mailgun-go.v1/cmd/mailgun/send.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/drhodes/golorem"
|
||||
"github.com/mailgun/log"
|
||||
"github.com/mailgun/mailgun-go"
|
||||
"github.com/thrawn01/args"
|
||||
)
|
||||
|
||||
func Send(parser *args.ArgParser, data interface{}) int {
|
||||
mg := data.(mailgun.Mailgun)
|
||||
var content []byte
|
||||
var err error
|
||||
var count int
|
||||
|
||||
log.InitWithConfig(log.Config{Name: "console"})
|
||||
desc := args.Dedent(`Send emails via the mailgun HTTP API
|
||||
|
||||
Examples:
|
||||
Post a simple email from stdin
|
||||
$ echo -n 'Hello World' | mailgun send -s "Test subject" address@example.com
|
||||
|
||||
Post a simple email to a specific domain
|
||||
$ echo -n 'Hello World' | mailgun send -s "Test subject" address@example.com -d my-domain.com
|
||||
|
||||
Post a test lorem ipsum email (random content, and subject)
|
||||
$ mailgun send --lorem address@example.com
|
||||
|
||||
Post a 10 random test lorem ipsum emails
|
||||
$ mailgun send --lorem address@example.com --count 10`)
|
||||
|
||||
parser.SetDesc(desc)
|
||||
parser.AddOption("--subject").Alias("-s").Help("subject of the message")
|
||||
parser.AddOption("--tags").IsStringSlice().Alias("-t").Help("comma separated list of tags")
|
||||
parser.AddOption("--from").Alias("-f").Env("FROM").Help("from address, defaults to <user>@<hostname>")
|
||||
parser.AddOption("--lorem").Alias("-l").IsTrue().Help("generate a randome subject and message content")
|
||||
parser.AddOption("--count").StoreInt(&count).Default("1").Alias("-c").Help("send the email x number of counts")
|
||||
parser.AddArgument("addresses").IsStringSlice().Required().Help("a list of email addresses")
|
||||
|
||||
opts := parser.ParseArgsSimple(nil)
|
||||
|
||||
// Required for send
|
||||
if err := opts.Required([]string{"domain", "api-key"}); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Missing Required option '%s'", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Default to user@hostname if no from address provided
|
||||
if !opts.IsSet("from") {
|
||||
host, err := os.Hostname()
|
||||
checkErr("Hostname Error", err)
|
||||
opts.Set("from", fmt.Sprintf("%s@%s", os.Getenv("USER"), host))
|
||||
}
|
||||
|
||||
// If stdin is not open and character device
|
||||
if args.IsCharDevice(os.Stdin) {
|
||||
// Read the content from stdin
|
||||
content, err = ioutil.ReadAll(os.Stdin)
|
||||
checkErr("Error reading stdin", err)
|
||||
}
|
||||
|
||||
subject := opts.String("subject")
|
||||
|
||||
if opts.Bool("lorem") {
|
||||
if len(subject) == 0 {
|
||||
subject = lorem.Sentence(3, 5)
|
||||
}
|
||||
if len(content) == 0 {
|
||||
content = []byte(lorem.Paragraph(10, 50))
|
||||
}
|
||||
} else {
|
||||
if len(content) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Must provide email body, or use --lorem")
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(subject) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Must provide subject, or use --lorem")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var tags []string
|
||||
if opts.IsSet("tags") {
|
||||
tags = opts.StringSlice("tags")
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
msg := mg.NewMessage(
|
||||
opts.String("from"),
|
||||
subject,
|
||||
string(content),
|
||||
opts.StringSlice("addresses")...)
|
||||
|
||||
// Add any tags if provided
|
||||
for _, tag := range tags {
|
||||
msg.AddTag(tag)
|
||||
}
|
||||
|
||||
resp, id, err := mg.Send(msg)
|
||||
checkErr("Message Error", err)
|
||||
fmt.Printf("Id: %s Resp: %s\n", id, resp)
|
||||
}
|
||||
return 0
|
||||
}
|
147
vendor/gopkg.in/mailgun/mailgun-go.v1/cmd/mailgun/tag.go
generated
vendored
Normal file
147
vendor/gopkg.in/mailgun/mailgun-go.v1/cmd/mailgun/tag.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/mailgun/log"
|
||||
"github.com/mailgun/mailgun-go"
|
||||
"github.com/thrawn01/args"
|
||||
)
|
||||
|
||||
func Tag(parser *args.ArgParser, data interface{}) int {
|
||||
mg := data.(mailgun.Mailgun)
|
||||
var err error
|
||||
|
||||
log.InitWithConfig(log.Config{Name: "console"})
|
||||
desc := args.Dedent(`Manage tags via the mailgun HTTP API
|
||||
|
||||
Examples:
|
||||
list all available tags
|
||||
$ mailgun tag list
|
||||
|
||||
list tags with a specific prefix
|
||||
$ mailgun tag list -p foo
|
||||
|
||||
get a single tag
|
||||
$ mailgun tag get my-tag
|
||||
|
||||
delete a tag
|
||||
$ mailgun tag delete my-tag`)
|
||||
|
||||
parser.SetDesc(desc)
|
||||
|
||||
// Commands
|
||||
parser.AddCommand("list", ListTag)
|
||||
parser.AddCommand("get", GetTag)
|
||||
parser.AddCommand("delete", DeleteTag)
|
||||
|
||||
// Parse the subcommands
|
||||
parser.ParseArgsSimple(nil)
|
||||
|
||||
// Run the command chosen by our user
|
||||
retCode, err := parser.RunCommand(mg)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
return 1
|
||||
}
|
||||
return retCode
|
||||
}
|
||||
|
||||
func ListTag(parser *args.ArgParser, data interface{}) int {
|
||||
mg := data.(mailgun.Mailgun)
|
||||
|
||||
desc := args.Dedent(`list tags via the mailgun HTTP API
|
||||
|
||||
Examples:
|
||||
list all available tags
|
||||
$ mailgun tag list
|
||||
|
||||
list the first 2,000 tags
|
||||
$ mailgun tag list -l 2000
|
||||
|
||||
list tags with a specific prefix
|
||||
$ mailgun tag list -p foo`)
|
||||
parser.SetDesc(desc)
|
||||
parser.AddOption("--prefix").Alias("-p").Help("list only tags with the given prefix")
|
||||
parser.AddOption("--limit").Alias("-l").IsInt().Help("Limit the result set")
|
||||
parser.AddOption("--tag").Alias("-t").Help("The tag that marks piviot point for the --page parameter")
|
||||
parser.AddOption("--page").Alias("-pg").
|
||||
Help("The page direction based off the tag parameter; valid choices are (first, last, next, prev)")
|
||||
|
||||
opts := parser.ParseArgsSimple(nil)
|
||||
|
||||
// Calculate our request limit
|
||||
limit := opts.Int("limit")
|
||||
|
||||
// Create the tag iterator
|
||||
it := mg.ListTags(&mailgun.TagOptions{
|
||||
Limit: limit,
|
||||
Prefix: opts.String("prefix"),
|
||||
Page: opts.String("page"),
|
||||
Tag: opts.String("tag"),
|
||||
})
|
||||
|
||||
var count int
|
||||
var page mailgun.TagsPage
|
||||
for it.Next(&page) {
|
||||
for _, tag := range page.Items {
|
||||
fmt.Printf("%s\n", tag.Value)
|
||||
count += 1
|
||||
if limit != 0 && count > limit {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func GetTag(parser *args.ArgParser, data interface{}) int {
|
||||
mg := data.(mailgun.Mailgun)
|
||||
|
||||
desc := args.Dedent(`get metatdata about a tag via the mailgun HTTP API
|
||||
|
||||
Examples:
|
||||
fetch the tag metatdata and print it in json
|
||||
$ mailgun tag get my-tag`)
|
||||
parser.SetDesc(desc)
|
||||
parser.AddArgument("tag").Required().Help("the tag to retrieve")
|
||||
|
||||
opts := parser.ParseArgsSimple(nil)
|
||||
|
||||
tag, err := mg.GetTag(opts.String("tag"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
|
||||
return 1
|
||||
}
|
||||
output, err := json.Marshal(tag)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Json Error: %s\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Print(string(output))
|
||||
return 0
|
||||
}
|
||||
|
||||
func DeleteTag(parser *args.ArgParser, data interface{}) int {
|
||||
mg := data.(mailgun.Mailgun)
|
||||
|
||||
desc := args.Dedent(`delete a tag via the mailgun HTTP API
|
||||
|
||||
Examples:
|
||||
delete my-tag
|
||||
$ mailgun tag delete my-tag`)
|
||||
parser.SetDesc(desc)
|
||||
parser.AddArgument("tag").Required().Help("the tag to delete")
|
||||
|
||||
opts := parser.ParseArgsSimple(nil)
|
||||
|
||||
err := mg.DeleteTag(opts.String("tag"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
79
vendor/gopkg.in/mailgun/mailgun-go.v1/credentials.go
generated
vendored
Normal file
79
vendor/gopkg.in/mailgun/mailgun-go.v1/credentials.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A Credential structure describes a principle allowed to send or receive mail at the domain.
|
||||
type Credential struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// ErrEmptyParam results occur when a required parameter is missing.
|
||||
var ErrEmptyParam = fmt.Errorf("empty or illegal parameter")
|
||||
|
||||
// GetCredentials returns the (possibly zero-length) list of credentials associated with your domain.
|
||||
func (mg *MailgunImpl) GetCredentials(limit, skip int) (int, []Credential, error) {
|
||||
r := newHTTPRequest(generateCredentialsUrl(mg, ""))
|
||||
r.setClient(mg.Client())
|
||||
if limit != DefaultLimit {
|
||||
r.addParameter("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if skip != DefaultSkip {
|
||||
r.addParameter("skip", strconv.Itoa(skip))
|
||||
}
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
var envelope struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Items []Credential `json:"items"`
|
||||
}
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
return envelope.TotalCount, envelope.Items, nil
|
||||
}
|
||||
|
||||
// CreateCredential attempts to create associate a new principle with your domain.
|
||||
func (mg *MailgunImpl) CreateCredential(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(r, p)
|
||||
return err
|
||||
}
|
||||
|
||||
// ChangeCredentialPassword attempts to alter the indicated credential's password.
|
||||
func (mg *MailgunImpl) ChangeCredentialPassword(id, password string) error {
|
||||
if (id == "") || (password == "") {
|
||||
return ErrEmptyParam
|
||||
}
|
||||
r := newHTTPRequest(generateCredentialsUrl(mg, id))
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
p := newUrlEncodedPayload()
|
||||
p.addValue("password", password)
|
||||
_, err := makePutRequest(r, p)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCredential attempts to remove the indicated principle from the domain.
|
||||
func (mg *MailgunImpl) DeleteCredential(id string) error {
|
||||
if id == "" {
|
||||
return ErrEmptyParam
|
||||
}
|
||||
r := newHTTPRequest(generateCredentialsUrl(mg, id))
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
37
vendor/gopkg.in/mailgun/mailgun-go.v1/credentials_test.go
generated
vendored
Normal file
37
vendor/gopkg.in/mailgun/mailgun-go.v1/credentials_test.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func TestGetCredentials(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
n, cs, err := mg.GetCredentials(DefaultLimit, DefaultSkip)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
t.Logf("Login\tCreated At\t\n")
|
||||
for _, c := range cs {
|
||||
t.Logf("%s\t%s\t\n", c.Login, c.CreatedAt)
|
||||
}
|
||||
t.Logf("%d credentials listed out of %d\n", len(cs), n)
|
||||
}
|
||||
|
||||
func TestCreateDeleteCredentials(t *testing.T) {
|
||||
domain := reqEnv(t, "MG_DOMAIN")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
randomPassword := randomString(16, "pw")
|
||||
randomID := strings.ToLower(randomString(16, "usr"))
|
||||
randomLogin := fmt.Sprintf("%s@%s", randomID, domain)
|
||||
|
||||
ensure.Nil(t, mg.CreateCredential(randomLogin, randomPassword))
|
||||
ensure.Nil(t, mg.ChangeCredentialPassword(randomID, randomString(16, "pw2")))
|
||||
ensure.Nil(t, mg.DeleteCredential(randomID))
|
||||
}
|
124
vendor/gopkg.in/mailgun/mailgun-go.v1/domains.go
generated
vendored
Normal file
124
vendor/gopkg.in/mailgun/mailgun-go.v1/domains.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultLimit and DefaultSkip instruct the SDK to rely on Mailgun's reasonable defaults for pagination settings.
|
||||
const (
|
||||
DefaultLimit = -1
|
||||
DefaultSkip = -1
|
||||
)
|
||||
|
||||
// Disabled, Tag, and Delete indicate spam actions.
|
||||
// Disabled prevents Mailgun from taking any action on what it perceives to be spam.
|
||||
// Tag instruments the received message with headers providing a measure of its spamness.
|
||||
// Delete instructs Mailgun to just block or delete the message all-together.
|
||||
const (
|
||||
Tag = "tag"
|
||||
Disabled = "disabled"
|
||||
Delete = "delete"
|
||||
)
|
||||
|
||||
// A Domain structure holds information about a domain used when sending mail.
|
||||
// The SpamAction field must be one of Tag, Disabled, or Delete.
|
||||
type Domain struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
SMTPLogin string `json:"smtp_login"`
|
||||
Name string `json:"name"`
|
||||
SMTPPassword string `json:"smtp_password"`
|
||||
Wildcard bool `json:"wildcard"`
|
||||
SpamAction string `json:"spam_action"`
|
||||
}
|
||||
|
||||
// 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 domainsEnvelope struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Items []Domain `json:"items"`
|
||||
}
|
||||
|
||||
type singleDomainEnvelope struct {
|
||||
Domain Domain `json:"domain"`
|
||||
ReceivingDNSRecords []DNSRecord `json:"receiving_dns_records"`
|
||||
SendingDNSRecords []DNSRecord `json:"sending_dns_records"`
|
||||
}
|
||||
|
||||
// GetCreatedAt returns the time the domain was created as a normal Go time.Time type.
|
||||
func (d Domain) GetCreatedAt() (t time.Time, err error) {
|
||||
t, err = parseMailgunTime(d.CreatedAt)
|
||||
return
|
||||
}
|
||||
|
||||
// GetDomains retrieves a set of domains from Mailgun.
|
||||
//
|
||||
// Assuming no error, both the number of items retrieved and a slice of Domain instances.
|
||||
// The number of items returned may be less than the specified limit, if it's specified.
|
||||
// Note that zero items and a zero-length slice do not necessarily imply an error occurred.
|
||||
// Except for the error itself, all results are undefined in the event of an error.
|
||||
func (m *MailgunImpl) GetDomains(limit, skip int) (int, []Domain, error) {
|
||||
r := newHTTPRequest(generatePublicApiUrl(m, domainsEndpoint))
|
||||
r.setClient(m.Client())
|
||||
if limit != DefaultLimit {
|
||||
r.addParameter("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if skip != DefaultSkip {
|
||||
r.addParameter("skip", strconv.Itoa(skip))
|
||||
}
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
var envelope domainsEnvelope
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
return envelope.TotalCount, envelope.Items, nil
|
||||
}
|
||||
|
||||
// Retrieve detailed information about the named domain.
|
||||
func (m *MailgunImpl) GetSingleDomain(domain string) (Domain, []DNSRecord, []DNSRecord, error) {
|
||||
r := newHTTPRequest(generatePublicApiUrl(m, domainsEndpoint) + "/" + domain)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
var envelope singleDomainEnvelope
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
return envelope.Domain, envelope.ReceivingDNSRecords, envelope.SendingDNSRecords, err
|
||||
}
|
||||
|
||||
// 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 (m *MailgunImpl) CreateDomain(name string, smtpPassword string, spamAction string, wildcard bool) error {
|
||||
r := newHTTPRequest(generatePublicApiUrl(m, domainsEndpoint))
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
payload := newUrlEncodedPayload()
|
||||
payload.addValue("name", name)
|
||||
payload.addValue("smtp_password", smtpPassword)
|
||||
payload.addValue("spam_action", spamAction)
|
||||
payload.addValue("wildcard", strconv.FormatBool(wildcard))
|
||||
_, err := makePostRequest(r, payload)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteDomain instructs Mailgun to dispose of the named domain name.
|
||||
func (m *MailgunImpl) DeleteDomain(name string) error {
|
||||
r := newHTTPRequest(generatePublicApiUrl(m, domainsEndpoint) + "/" + name)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
64
vendor/gopkg.in/mailgun/mailgun-go.v1/domains_test.go
generated
vendored
Normal file
64
vendor/gopkg.in/mailgun/mailgun-go.v1/domains_test.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func TestGetDomains(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
n, domains, err := mg.GetDomains(DefaultLimit, DefaultSkip)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
t.Logf("TestGetDomains: %d domains retrieved\n", n)
|
||||
for _, d := range domains {
|
||||
t.Logf("TestGetDomains: %#v\n", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSingleDomain(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
_, domains, err := mg.GetDomains(DefaultLimit, DefaultSkip)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
dr, rxDnsRecords, txDnsRecords, err := mg.GetSingleDomain(domains[0].Name)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
t.Logf("TestGetSingleDomain: %#v\n", dr)
|
||||
for _, rxd := range rxDnsRecords {
|
||||
t.Logf("TestGetSingleDomains: %#v\n", rxd)
|
||||
}
|
||||
for _, txd := range txDnsRecords {
|
||||
t.Logf("TestGetSingleDomains: %#v\n", txd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSingleDomainNotExist(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
_, _, _, err = mg.GetSingleDomain(randomString(32, "com.edu.org.") + ".com")
|
||||
if err == nil {
|
||||
t.Fatal("Did not expect a domain to exist")
|
||||
}
|
||||
ure, ok := err.(*UnexpectedResponseError)
|
||||
ensure.True(t, ok)
|
||||
ensure.DeepEqual(t, ure.Actual, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestAddDeleteDomain(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
// First, we need to add the domain.
|
||||
randomDomainName := randomString(16, "DOMAIN") + ".example.com"
|
||||
randomPassword := randomString(16, "PASSWD")
|
||||
ensure.Nil(t, mg.CreateDomain(randomDomainName, randomPassword, Tag, false))
|
||||
// Next, we delete it.
|
||||
ensure.Nil(t, mg.DeleteDomain(randomDomainName))
|
||||
}
|
72
vendor/gopkg.in/mailgun/mailgun-go.v1/email_validation.go
generated
vendored
Normal file
72
vendor/gopkg.in/mailgun/mailgun-go.v1/email_validation.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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.
|
||||
//
|
||||
// IsValid indicates whether an email address conforms to IETF RFC standards.
|
||||
// Parts records the different subfields of an email address.
|
||||
// Address echoes the address provided.
|
||||
// DidYouMean provides a simple recommendation in case the address is invalid or
|
||||
// Mailgun thinks you might have a typo.
|
||||
// DidYouMean may be empty (""), in which case Mailgun has no recommendation to give.
|
||||
// The existence of DidYouMean does NOT imply the email provided has anything wrong with it.
|
||||
type EmailVerification struct {
|
||||
IsValid bool `json:"is_valid"`
|
||||
Parts EmailVerificationParts `json:"parts"`
|
||||
Address string `json:"address"`
|
||||
DidYouMean string `json:"did_you_mean"`
|
||||
}
|
||||
|
||||
type addressParseResult struct {
|
||||
Parsed []string `json:"parsed"`
|
||||
Unparseable []string `json:"unparseable"`
|
||||
}
|
||||
|
||||
// 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. (See example.)
|
||||
// NOTE: Use of this function requires a proper public API key. The private API key will not work.
|
||||
func (m *MailgunImpl) ValidateEmail(email string) (EmailVerification, error) {
|
||||
r := newHTTPRequest(generatePublicApiUrl(m, addressValidateEndpoint))
|
||||
r.setClient(m.Client())
|
||||
r.addParameter("address", email)
|
||||
r.setBasicAuth(basicAuthUser, m.PublicApiKey())
|
||||
|
||||
var response EmailVerification
|
||||
err := getResponseFromJSON(r, &response)
|
||||
if err != nil {
|
||||
return EmailVerification{}, err
|
||||
}
|
||||
|
||||
return response, 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 *MailgunImpl) ParseAddresses(addresses ...string) ([]string, []string, error) {
|
||||
r := newHTTPRequest(generatePublicApiUrl(m, addressParseEndpoint))
|
||||
r.setClient(m.Client())
|
||||
r.addParameter("addresses", strings.Join(addresses, ","))
|
||||
r.setBasicAuth(basicAuthUser, m.PublicApiKey())
|
||||
|
||||
var response addressParseResult
|
||||
err := getResponseFromJSON(r, &response)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return response.Parsed, response.Unparseable, nil
|
||||
}
|
41
vendor/gopkg.in/mailgun/mailgun-go.v1/email_validation_test.go
generated
vendored
Normal file
41
vendor/gopkg.in/mailgun/mailgun-go.v1/email_validation_test.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func TestEmailValidation(t *testing.T) {
|
||||
reqEnv(t, "MG_PUBLIC_API_KEY")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
ev, err := mg.ValidateEmail("foo@mailgun.com")
|
||||
ensure.Nil(t, err)
|
||||
|
||||
ensure.True(t, ev.IsValid)
|
||||
ensure.True(t, ev.Parts.DisplayName == "")
|
||||
ensure.DeepEqual(t, ev.Parts.LocalPart, "foo")
|
||||
ensure.DeepEqual(t, ev.Parts.Domain, "mailgun.com")
|
||||
}
|
||||
|
||||
func TestParseAddresses(t *testing.T) {
|
||||
reqEnv(t, "MG_PUBLIC_API_KEY")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
addressesThatParsed, unparsableAddresses, err := mg.ParseAddresses(
|
||||
"Alice <alice@example.com>",
|
||||
"bob@example.com",
|
||||
"example.com")
|
||||
ensure.Nil(t, err)
|
||||
hittest := map[string]bool{
|
||||
"Alice <alice@example.com>": true,
|
||||
"bob@example.com": true,
|
||||
}
|
||||
for _, a := range addressesThatParsed {
|
||||
ensure.True(t, hittest[a])
|
||||
}
|
||||
ensure.True(t, len(unparsableAddresses) == 1)
|
||||
}
|
418
vendor/gopkg.in/mailgun/mailgun-go.v1/events.go
generated
vendored
Normal file
418
vendor/gopkg.in/mailgun/mailgun-go.v1/events.go
generated
vendored
Normal file
@@ -0,0 +1,418 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Events are open-ended, loosely-defined JSON documents.
|
||||
// They will always have an event and a timestamp field, however.
|
||||
type Event map[string]interface{}
|
||||
|
||||
// Parse the timestamp field for this event into a Time object
|
||||
func (e Event) ParseTimeStamp() (time.Time, error) {
|
||||
obj, ok := e["timestamp"]
|
||||
if !ok {
|
||||
return time.Time{}, errors.New("'timestamp' field not found in event")
|
||||
}
|
||||
timestamp, ok := obj.(float64)
|
||||
if !ok {
|
||||
return time.Time{}, errors.New("'timestamp' field not a float64")
|
||||
}
|
||||
microseconds := int64(timestamp * 1000000)
|
||||
return time.Unix(0, microseconds*int64(time.Microsecond/time.Nanosecond)).UTC(), nil
|
||||
}
|
||||
|
||||
func (e Event) ParseMessageId() (string, error) {
|
||||
message, err := toMapInterface("message", e)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
headers, err := toMapInterface("headers", message)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return headers["message-id"].(string), nil
|
||||
}
|
||||
|
||||
// noTime always equals an uninitialized Time structure.
|
||||
// It's used to detect when a time parameter is provided.
|
||||
var noTime time.Time
|
||||
|
||||
// GetEventsOptions lets the caller of GetEvents() specify how the results are to be returned.
|
||||
// Begin and End time-box the results returned.
|
||||
// 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.
|
||||
// Limit caps the number of results returned. If left unspecified, Mailgun assumes 100.
|
||||
// Compact, if true, compacts the returned JSON to minimize transmission bandwidth.
|
||||
// Otherwise, the JSON is spaced appropriately for human consumption.
|
||||
// Filter allows the caller to provide more specialized filters on the query.
|
||||
// Consult the Mailgun documentation for more details.
|
||||
type EventsOptions struct {
|
||||
Begin, End time.Time
|
||||
ForceAscending, ForceDescending, Compact bool
|
||||
Limit int
|
||||
Filter map[string]string
|
||||
ThresholdAge time.Duration
|
||||
PollInterval time.Duration
|
||||
}
|
||||
|
||||
// Depreciated See `ListEvents()`
|
||||
type GetEventsOptions struct {
|
||||
Begin, End time.Time
|
||||
ForceAscending, ForceDescending, Compact bool
|
||||
Limit int
|
||||
Filter map[string]string
|
||||
}
|
||||
|
||||
// EventIterator maintains the state necessary for paging though small parcels of a larger set of events.
|
||||
type EventIterator struct {
|
||||
events []Event
|
||||
NextURL, PrevURL, FirstURL, LastURL string
|
||||
mg Mailgun
|
||||
err error
|
||||
}
|
||||
|
||||
// NewEventIterator creates a new iterator for events.
|
||||
// Use GetFirstPage to retrieve the first batch of events.
|
||||
// Use GetNext and GetPrevious thereafter as appropriate to iterate through sets of data.
|
||||
//
|
||||
// *This call is Deprecated, use ListEvents() instead*
|
||||
func (mg *MailgunImpl) NewEventIterator() *EventIterator {
|
||||
return &EventIterator{mg: mg}
|
||||
}
|
||||
|
||||
// Create an new iterator to fetch a page of events from the events api
|
||||
// it := mg.ListEvents(EventsOptions{})
|
||||
// var events []Event
|
||||
// for it.Next(&events) {
|
||||
// for _, event := range events {
|
||||
// // Do things with events
|
||||
// }
|
||||
// }
|
||||
// if it.Err() != nil {
|
||||
// log.Fatal(it.Err())
|
||||
// }
|
||||
func (mg *MailgunImpl) ListEvents(opts *EventsOptions) *EventIterator {
|
||||
req := newHTTPRequest(generateApiUrl(mg, eventsEndpoint))
|
||||
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")
|
||||
}
|
||||
if opts.ForceDescending {
|
||||
req.addParameter("ascending", "no")
|
||||
}
|
||||
if opts.Begin != noTime {
|
||||
req.addParameter("begin", formatMailgunTime(&opts.Begin))
|
||||
}
|
||||
if opts.End != noTime {
|
||||
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,
|
||||
NextURL: url,
|
||||
FirstURL: url,
|
||||
PrevURL: "",
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// If an error occurred during iteration `Err()` will return non nil
|
||||
func (ei *EventIterator) Err() error {
|
||||
return ei.err
|
||||
}
|
||||
|
||||
// Events returns the most recently retrieved batch of events.
|
||||
// The length is guaranteed to fall between 0 and the limit set in the GetEventsOptions structure passed to GetFirstPage.
|
||||
func (ei *EventIterator) Events() []Event {
|
||||
return ei.events
|
||||
}
|
||||
|
||||
// GetFirstPage retrieves the first batch of events, according to your criteria.
|
||||
// See the GetEventsOptions structure for more details on how the fields affect the data returned.
|
||||
func (ei *EventIterator) GetFirstPage(opts GetEventsOptions) error {
|
||||
if opts.ForceAscending && opts.ForceDescending {
|
||||
return fmt.Errorf("collation cannot at once be both ascending and descending")
|
||||
}
|
||||
|
||||
payload := newUrlEncodedPayload()
|
||||
if opts.Limit != 0 {
|
||||
payload.addValue("limit", fmt.Sprintf("%d", opts.Limit))
|
||||
}
|
||||
if opts.Compact {
|
||||
payload.addValue("pretty", "no")
|
||||
}
|
||||
if opts.ForceAscending {
|
||||
payload.addValue("ascending", "yes")
|
||||
}
|
||||
if opts.ForceDescending {
|
||||
payload.addValue("ascending", "no")
|
||||
}
|
||||
if opts.Begin != noTime {
|
||||
payload.addValue("begin", formatMailgunTime(&opts.Begin))
|
||||
}
|
||||
if opts.End != noTime {
|
||||
payload.addValue("end", formatMailgunTime(&opts.End))
|
||||
}
|
||||
if opts.Filter != nil {
|
||||
for k, v := range opts.Filter {
|
||||
payload.addValue(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
url, err := generateParameterizedUrl(ei.mg, eventsEndpoint, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ei.fetch(url)
|
||||
}
|
||||
|
||||
// Retrieves the chronologically previous batch of events, if any exist.
|
||||
// You know you're at the end of the list when len(Events())==0.
|
||||
func (ei *EventIterator) GetPrevious() error {
|
||||
return ei.fetch(ei.PrevURL)
|
||||
}
|
||||
|
||||
// Retrieves the chronologically next batch of events, if any exist.
|
||||
// You know you're at the end of the list when len(Events())==0.
|
||||
func (ei *EventIterator) GetNext() error {
|
||||
return ei.fetch(ei.NextURL)
|
||||
}
|
||||
|
||||
// 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(events *[]Event) bool {
|
||||
if ei.err != nil {
|
||||
return false
|
||||
}
|
||||
ei.err = ei.fetch(ei.NextURL)
|
||||
if ei.err != nil {
|
||||
return false
|
||||
}
|
||||
*events = ei.events
|
||||
if len(ei.events) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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(events *[]Event) bool {
|
||||
if ei.err != nil {
|
||||
return false
|
||||
}
|
||||
ei.err = ei.fetch(ei.FirstURL)
|
||||
if ei.err != nil {
|
||||
return false
|
||||
}
|
||||
*events = ei.events
|
||||
return true
|
||||
}
|
||||
|
||||
// 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(events *[]Event) bool {
|
||||
if ei.err != nil {
|
||||
return false
|
||||
}
|
||||
ei.err = ei.fetch(ei.LastURL)
|
||||
if ei.err != nil {
|
||||
return false
|
||||
}
|
||||
*events = ei.events
|
||||
return true
|
||||
}
|
||||
|
||||
// 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(events *[]Event) bool {
|
||||
if ei.err != nil {
|
||||
return false
|
||||
}
|
||||
if ei.PrevURL == "" {
|
||||
return false
|
||||
}
|
||||
ei.err = ei.fetch(ei.PrevURL)
|
||||
if ei.err != nil {
|
||||
return false
|
||||
}
|
||||
*events = ei.events
|
||||
if len(ei.events) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// EventPoller maintains the state necessary for polling events
|
||||
type EventPoller struct {
|
||||
it *EventIterator
|
||||
opts EventsOptions
|
||||
thresholdTime time.Time
|
||||
sleepUntil time.Time
|
||||
mg Mailgun
|
||||
err error
|
||||
}
|
||||
|
||||
// Poll the events api and return new events as they occur
|
||||
// it = mg.PollEvents(&EventsOptions{
|
||||
// // Poll() returns after this threshold is met, or events older than this threshold appear
|
||||
// ThresholdAge: time.Second * 10,
|
||||
// // 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
|
||||
// // Blocks until new events appear
|
||||
// for it.Poll(&events) {
|
||||
// for _, event := range(events) {
|
||||
// fmt.Printf("Event %+v\n", event)
|
||||
// }
|
||||
// }
|
||||
// if it.Err() != nil {
|
||||
// log.Fatal(it.Err())
|
||||
// }
|
||||
func (mg *MailgunImpl) PollEvents(opts *EventsOptions) *EventPoller {
|
||||
now := time.Now()
|
||||
// ForceAscending must be set
|
||||
opts.ForceAscending = true
|
||||
|
||||
// Default begin time is 30 minutes ago
|
||||
if opts.Begin == noTime {
|
||||
opts.Begin = now.Add(time.Minute * -30)
|
||||
}
|
||||
|
||||
// Default threshold age is 30 minutes
|
||||
if opts.ThresholdAge.Nanoseconds() == 0 {
|
||||
opts.ThresholdAge = time.Duration(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(events *[]Event) bool {
|
||||
var currentPage string
|
||||
ep.thresholdTime = time.Now().UTC().Add(ep.opts.ThresholdAge)
|
||||
for {
|
||||
if ep.sleepUntil != noTime {
|
||||
// Sleep the rest of our duration
|
||||
time.Sleep(ep.sleepUntil.Sub(time.Now()))
|
||||
}
|
||||
|
||||
// Remember our current page url
|
||||
currentPage = ep.it.NextURL
|
||||
|
||||
// Attempt to get a page of events
|
||||
var page []Event
|
||||
if ep.it.Next(&page) == false {
|
||||
if ep.it.Err() == nil && len(page) == 0 {
|
||||
// No events, sleep for our poll interval
|
||||
ep.sleepUntil = time.Now().Add(ep.opts.PollInterval)
|
||||
continue
|
||||
}
|
||||
ep.err = ep.it.Err()
|
||||
return false
|
||||
}
|
||||
|
||||
// Last event on the page
|
||||
lastEvent := page[len(page)-1]
|
||||
|
||||
timeStamp, err := lastEvent.ParseTimeStamp()
|
||||
if err != nil {
|
||||
ep.err = errors.Wrap(err, "event timestamp error")
|
||||
return false
|
||||
}
|
||||
// Record the next time we should query for new events
|
||||
ep.sleepUntil = time.Now().Add(ep.opts.PollInterval)
|
||||
|
||||
// If the last event on the page is older than our threshold time
|
||||
// or we have been polling for longer than our threshold time
|
||||
if timeStamp.After(ep.thresholdTime) || time.Now().UTC().After(ep.thresholdTime) {
|
||||
ep.thresholdTime = time.Now().UTC().Add(ep.opts.ThresholdAge)
|
||||
// Return the page of events to the user
|
||||
*events = page
|
||||
return true
|
||||
}
|
||||
// Since we didn't find an event older than our
|
||||
// threshold, fetch this same page again
|
||||
ep.it.NextURL = currentPage
|
||||
}
|
||||
}
|
||||
|
||||
// GetFirstPage, GetPrevious, and GetNext all have a common body of code.
|
||||
// fetch completes the API fetch common to all three of these functions.
|
||||
func (ei *EventIterator) fetch(url string) error {
|
||||
r := newHTTPRequest(url)
|
||||
r.setClient(ei.mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, ei.mg.ApiKey())
|
||||
var response map[string]interface{}
|
||||
err := getResponseFromJSON(r, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := response["items"].([]interface{})
|
||||
ei.events = make([]Event, len(items))
|
||||
for i, item := range items {
|
||||
ei.events[i] = item.(map[string]interface{})
|
||||
}
|
||||
|
||||
pagings := response["paging"].(map[string]interface{})
|
||||
links := make(map[string]string, len(pagings))
|
||||
for key, page := range pagings {
|
||||
links[key] = page.(string)
|
||||
}
|
||||
ei.NextURL = links["next"]
|
||||
ei.PrevURL = links["previous"]
|
||||
ei.FirstURL = links["first"]
|
||||
ei.LastURL = links["last"]
|
||||
return err
|
||||
}
|
||||
|
||||
func toMapInterface(field string, thingy map[string]interface{}) (map[string]interface{}, error) {
|
||||
var empty map[string]interface{}
|
||||
obj, ok := thingy[field]
|
||||
if !ok {
|
||||
return empty, errors.Errorf("'%s' field not found in event", field)
|
||||
}
|
||||
result, ok := obj.(map[string]interface{})
|
||||
if !ok {
|
||||
return empty, errors.Errorf("'%s' field not a map[string]interface{}", field)
|
||||
}
|
||||
return result, nil
|
||||
}
|
239
vendor/gopkg.in/mailgun/mailgun-go.v1/events_test.go
generated
vendored
Normal file
239
vendor/gopkg.in/mailgun/mailgun-go.v1/events_test.go
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("ListEvents()", func() {
|
||||
var t GinkgoTInterface
|
||||
var it *EventIterator
|
||||
var mg Mailgun
|
||||
var err error
|
||||
|
||||
BeforeEach(func() {
|
||||
t = GinkgoT()
|
||||
mg, err = NewMailgunFromEnv()
|
||||
Expect(err).To(BeNil())
|
||||
it = mg.ListEvents(&EventsOptions{Limit: 5})
|
||||
})
|
||||
|
||||
Describe("it.Next()", func() {
|
||||
It("Should iterate forward through pages of events", func() {
|
||||
var firstPage, secondPage []Event
|
||||
|
||||
ensure.True(t, it.Next(&firstPage))
|
||||
ensure.True(t, it.NextURL != "")
|
||||
ensure.True(t, len(firstPage) != 0)
|
||||
firstIterator := *it
|
||||
|
||||
ensure.True(t, it.Next(&secondPage))
|
||||
ensure.True(t, len(secondPage) != 0)
|
||||
|
||||
// Pages should be different
|
||||
ensure.NotDeepEqual(t, firstPage, secondPage)
|
||||
ensure.True(t, firstIterator.NextURL != it.NextURL)
|
||||
ensure.True(t, firstIterator.PrevURL != it.PrevURL)
|
||||
ensure.Nil(t, it.Err())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("it.Previous()", func() {
|
||||
It("Should iterate backward through pages of events", func() {
|
||||
var firstPage, secondPage, previousPage []Event
|
||||
ensure.True(t, it.Next(&firstPage))
|
||||
ensure.True(t, it.Next(&secondPage))
|
||||
|
||||
ensure.True(t, it.Previous(&previousPage))
|
||||
ensure.True(t, len(previousPage) != 0)
|
||||
ensure.DeepEqual(t, previousPage, firstPage)
|
||||
})
|
||||
})
|
||||
|
||||
Describe("it.First()", func() {
|
||||
It("Should retrieve the first page of events", func() {
|
||||
var firstPage, secondPage []Event
|
||||
ensure.True(t, it.First(&firstPage))
|
||||
ensure.True(t, len(firstPage) != 0)
|
||||
|
||||
// Calling first resets the iterator to the first page
|
||||
ensure.True(t, it.Next(&secondPage))
|
||||
ensure.NotDeepEqual(t, firstPage, secondPage)
|
||||
})
|
||||
})
|
||||
|
||||
Describe("it.Last()", func() {
|
||||
Context("If First() or Next() was not called first", func() {
|
||||
It("Should fail with error", func() {
|
||||
var lastPage []Event
|
||||
// Calling Last() is invalid unless you first use First() or Next()
|
||||
ensure.False(t, it.Last(&lastPage))
|
||||
ensure.True(t, len(lastPage) == 0)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
Describe("it.Last()", func() {
|
||||
It("Should retrieve the last page of events", func() {
|
||||
var firstPage, lastPage, previousPage []Event
|
||||
ensure.True(t, it.Next(&firstPage))
|
||||
ensure.True(t, len(firstPage) != 0)
|
||||
|
||||
ensure.True(t, it.Last(&lastPage))
|
||||
ensure.True(t, len(lastPage) != 0)
|
||||
|
||||
// Calling first resets the iterator to the first page
|
||||
ensure.True(t, it.Previous(&previousPage))
|
||||
ensure.NotDeepEqual(t, lastPage, previousPage)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("EventIterator()", func() {
|
||||
log := log.New(GinkgoWriter, "EventIterator() - ", 0)
|
||||
var t GinkgoTInterface
|
||||
var mg Mailgun
|
||||
var err error
|
||||
|
||||
BeforeEach(func() {
|
||||
t = GinkgoT()
|
||||
mg, err = NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
})
|
||||
|
||||
Describe("GetFirstPage()", func() {
|
||||
Context("When no parameters are supplied", func() {
|
||||
It("Should return a list of events", func() {
|
||||
ei := mg.NewEventIterator()
|
||||
err := ei.GetFirstPage(GetEventsOptions{})
|
||||
ensure.Nil(t, err)
|
||||
|
||||
// Print out the kind of event and timestamp.
|
||||
// Specifics about each event will depend on the "event" type.
|
||||
events := ei.Events()
|
||||
log.Printf("Event\tTimestamp\t")
|
||||
for _, event := range events {
|
||||
log.Printf("%s\t%v\t\n", event["event"], event["timestamp"])
|
||||
}
|
||||
log.Printf("%d events dumped\n\n", len(events))
|
||||
ensure.True(t, len(events) != 0)
|
||||
|
||||
// TODO: (thrawn01) The more I look at this and test it,
|
||||
// the more I doubt it will ever work consistently
|
||||
//ei.GetPrevious()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Event{}", func() {
|
||||
var t GinkgoTInterface
|
||||
|
||||
BeforeEach(func() {
|
||||
t = GinkgoT()
|
||||
})
|
||||
|
||||
Describe("ParseTimeStamp()", func() {
|
||||
Context("When 'timestamp' exists and is valid", func() {
|
||||
It("Should parse the timestamp into time.Time{}", func() {
|
||||
event := Event{
|
||||
"timestamp": 1476380259.578017,
|
||||
}
|
||||
timestamp, err := event.ParseTimeStamp()
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, timestamp, time.Date(2016, 10, 13, 17, 37, 39,
|
||||
578017*int(time.Microsecond/time.Nanosecond), time.UTC))
|
||||
|
||||
event = Event{
|
||||
"timestamp": 1377211256.096436,
|
||||
}
|
||||
timestamp, err = event.ParseTimeStamp()
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, timestamp, time.Date(2013, 8, 22, 22, 40, 56,
|
||||
96436*int(time.Microsecond/time.Nanosecond), time.UTC))
|
||||
})
|
||||
})
|
||||
Context("When 'timestamp' is missing", func() {
|
||||
It("Should return error", func() {
|
||||
event := Event{
|
||||
"blah": "",
|
||||
}
|
||||
_, err := event.ParseTimeStamp()
|
||||
ensure.NotNil(t, err)
|
||||
ensure.DeepEqual(t, err.Error(), "'timestamp' field not found in event")
|
||||
})
|
||||
})
|
||||
Context("When 'timestamp' is not a float64", func() {
|
||||
It("Should return error", func() {
|
||||
event := Event{
|
||||
"timestamp": "1476380259.578017",
|
||||
}
|
||||
_, err := event.ParseTimeStamp()
|
||||
ensure.NotNil(t, err)
|
||||
ensure.DeepEqual(t, err.Error(), "'timestamp' field not a float64")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("PollEvents()", func() {
|
||||
log := log.New(GinkgoWriter, "PollEvents() - ", 0)
|
||||
var t GinkgoTInterface
|
||||
var it *EventPoller
|
||||
var mg Mailgun
|
||||
var err error
|
||||
|
||||
BeforeEach(func() {
|
||||
t = GinkgoT()
|
||||
})
|
||||
|
||||
Describe("it.Poll()", func() {
|
||||
It("Should return events once the threshold age has expired", func() {
|
||||
mg, err = NewMailgunFromEnv()
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// Very short poll interval
|
||||
it = mg.PollEvents(&EventsOptions{
|
||||
// Poll() returns after this threshold is met
|
||||
// or events older than this threshold appear
|
||||
ThresholdAge: time.Second * 10,
|
||||
// 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})
|
||||
|
||||
// Send an email
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
log.Printf("New Email: %s Id: %s\n", msg, id)
|
||||
|
||||
// Wait for our email event to arrive
|
||||
var events []Event
|
||||
it.Poll(&events)
|
||||
|
||||
var found bool
|
||||
// Log the events we received
|
||||
for _, event := range events {
|
||||
eventMsg, _ := event.ParseMessageId()
|
||||
timeStamp, _ := event.ParseTimeStamp()
|
||||
log.Printf("Event: %s <%s> - %s", eventMsg, event["event"], timeStamp)
|
||||
|
||||
// If we find our accepted email event
|
||||
if id == ("<"+eventMsg+">") && event["event"] == "accepted" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
// Ensure we found our email
|
||||
ensure.Nil(t, it.Err())
|
||||
ensure.True(t, found)
|
||||
})
|
||||
})
|
||||
})
|
117
vendor/gopkg.in/mailgun/mailgun-go.v1/examples_test.go
generated
vendored
Normal file
117
vendor/gopkg.in/mailgun/mailgun-go.v1/examples_test.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleMailgunImpl_ValidateEmail() {
|
||||
mg := NewMailgun("example.com", "", "my_public_api_key")
|
||||
ev, err := mg.ValidateEmail("joe@example.com")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if !ev.IsValid {
|
||||
log.Fatal("Expected valid e-mail address")
|
||||
}
|
||||
log.Printf("Parts local_part=%s domain=%s display_name=%s", ev.Parts.LocalPart, ev.Parts.Domain, ev.Parts.DisplayName)
|
||||
if ev.DidYouMean != "" {
|
||||
log.Printf("The address is syntactically valid, but perhaps has a typo.")
|
||||
log.Printf("Did you mean %s instead?", ev.DidYouMean)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMailgunImpl_ParseAddresses() {
|
||||
mg := NewMailgun("example.com", "", "my_public_api_key")
|
||||
addressesThatParsed, unparsableAddresses, err := mg.ParseAddresses("Alice <alice@example.com>", "bob@example.com", "example.com")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
hittest := map[string]bool{
|
||||
"Alice <alice@example.com>": true,
|
||||
"bob@example.com": true,
|
||||
}
|
||||
for _, a := range addressesThatParsed {
|
||||
if !hittest[a] {
|
||||
log.Fatalf("Expected %s to be parsable", a)
|
||||
}
|
||||
}
|
||||
if len(unparsableAddresses) != 1 {
|
||||
log.Fatalf("Expected 1 address to be unparsable; got %d", len(unparsableAddresses))
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMailgunImpl_UpdateList() {
|
||||
mg := NewMailgun("example.com", "my_api_key", "")
|
||||
_, err := mg.UpdateList("joe-stat@example.com", List{
|
||||
Name: "Joe Stat",
|
||||
Description: "Joe's status report list",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMailgunImpl_Send_constructed() {
|
||||
mg := NewMailgun("example.com", "my_api_key", "")
|
||||
m := NewMessage(
|
||||
"Excited User <me@example.com>",
|
||||
"Hello World",
|
||||
"Testing some Mailgun Awesomeness!",
|
||||
"baz@example.com",
|
||||
"bar@example.com",
|
||||
)
|
||||
m.SetTracking(true)
|
||||
m.SetDeliveryTime(time.Now().Add(24 * time.Hour))
|
||||
m.SetHtml("<html><body><h1>Testing some Mailgun Awesomeness!!</h1></body></html>")
|
||||
_, id, err := mg.Send(m)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Message id=%s", id)
|
||||
}
|
||||
|
||||
func ExampleMailgunImpl_Send_mime() {
|
||||
exampleMime := `Content-Type: text/plain; charset="ascii"
|
||||
Subject: Joe's Example Subject
|
||||
From: Joe Example <joe@example.com>
|
||||
To: BARGLEGARF <bargle.garf@example.com>
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Date: Thu, 6 Mar 2014 00:37:52 +0000
|
||||
|
||||
Testing some Mailgun MIME awesomeness!
|
||||
`
|
||||
mg := NewMailgun("example.com", "my_api_key", "")
|
||||
m := NewMIMEMessage(ioutil.NopCloser(strings.NewReader(exampleMime)), "bargle.garf@example.com")
|
||||
_, id, err := mg.Send(m)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Message id=%s", id)
|
||||
}
|
||||
|
||||
func ExampleMailgunImpl_GetRoutes() {
|
||||
mg := NewMailgun("example.com", "my_api_key", "")
|
||||
n, routes, err := mg.GetRoutes(DefaultLimit, DefaultSkip)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n > len(routes) {
|
||||
log.Printf("More routes exist than has been returned.")
|
||||
}
|
||||
for _, r := range routes {
|
||||
log.Printf("Route pri=%d expr=%s desc=%s", r.Priority, r.Expression, r.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMailgunImpl_UpdateRoute() {
|
||||
mg := NewMailgun("example.com", "my_api_key", "")
|
||||
_, err := mg.UpdateRoute("route-id-here", Route{
|
||||
Priority: 2,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
286
vendor/gopkg.in/mailgun/mailgun-go.v1/httphelpers.go
generated
vendored
Normal file
286
vendor/gopkg.in/mailgun/mailgun-go.v1/httphelpers.go
generated
vendored
Normal file
@@ -0,0 +1,286 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 formDataPayload struct {
|
||||
contentType string
|
||||
Values []keyValuePair
|
||||
Files []keyValuePair
|
||||
ReadClosers []keyNameRC
|
||||
}
|
||||
|
||||
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) 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
|
||||
}
|
||||
}
|
||||
|
||||
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() (*httpResponse, error) {
|
||||
return r.makeRequest("GET", nil)
|
||||
}
|
||||
|
||||
func (r *httpRequest) makePostRequest(payload payload) (*httpResponse, error) {
|
||||
return r.makeRequest("POST", payload)
|
||||
}
|
||||
|
||||
func (r *httpRequest) makePutRequest(payload payload) (*httpResponse, error) {
|
||||
return r.makeRequest("PUT", payload)
|
||||
}
|
||||
|
||||
func (r *httpRequest) makeDeleteRequest() (*httpResponse, error) {
|
||||
return r.makeRequest("DELETE", nil)
|
||||
}
|
||||
|
||||
func (r *httpRequest) makeRequest(method string, payload payload) (*httpResponse, 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
responseBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response.Data = responseBody
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (r *httpRequest) generateUrlWithParameters() (string, error) {
|
||||
url, err := url.Parse(r.URL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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, " ")
|
||||
}
|
359
vendor/gopkg.in/mailgun/mailgun-go.v1/mailgun.go
generated
vendored
Normal file
359
vendor/gopkg.in/mailgun/mailgun-go.v1/mailgun.go
generated
vendored
Normal file
@@ -0,0 +1,359 @@
|
||||
// TODO(sfalvo):
|
||||
// Document how to run acceptance tests.
|
||||
|
||||
// The mailgun package 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>
|
||||
// Version: 0.99.0
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// This document includes a number of examples which illustrates some aspects of the GUI which might be misleading or confusing.
|
||||
// All examples included are derived from an acceptance test.
|
||||
// Note that every SDK function has a corresponding acceptance test, so
|
||||
// if you don't find an example for a function you'd like to know more about,
|
||||
// please check the acceptance sub-package 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.
|
||||
//
|
||||
// Limit and Skip Settings
|
||||
//
|
||||
// Many SDK functions consume a pair of parameters called limit and skip.
|
||||
// These help control how much data Mailgun sends over the wire.
|
||||
// Limit, as you'd expect, gives a count of the number of records you want to receive.
|
||||
// Note that, at present, Mailgun imposes its own cap of 100, for all API endpoints.
|
||||
// Skip indicates where in the data set you want to start receiving from.
|
||||
// Mailgun defaults to the very beginning of the dataset if not specified explicitly.
|
||||
//
|
||||
// If you don't particularly care how much data you receive, you may specify DefaultLimit.
|
||||
// If you similarly don't care about where the data starts, you may specify DefaultSkip.
|
||||
//
|
||||
// Functions that Return Totals
|
||||
//
|
||||
// Functions which accept a limit and skip setting, in general,
|
||||
// will also return a total count of the items returned.
|
||||
// Note that this total count is not the total in the bundle returned by the call.
|
||||
// You can determine that easily enough with Go's len() function.
|
||||
// The total that you receive actually refers to the complete set of data on the server.
|
||||
// This total may well exceed the size returned from the API.
|
||||
//
|
||||
// If this happens, you may find yourself needing to iterate over the dataset of interest.
|
||||
// For example:
|
||||
//
|
||||
// // Get total amount of stuff we have to work with.
|
||||
// mg := NewMailgun("example.com", "my_api_key", "")
|
||||
// n, _, err := mg.GetStats(1, 0, nil, "sent", "opened")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// // Loop over it all.
|
||||
// for sk := 0; sk < n; sk += limit {
|
||||
// _, stats, err := mg.GetStats(limit, sk, nil, "sent", "opened")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// doSomethingWith(stats)
|
||||
// }
|
||||
//
|
||||
// License
|
||||
//
|
||||
// Copyright (c) 2013-2014, 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Debug = false
|
||||
|
||||
const (
|
||||
ApiBase = "https://api.mailgun.net/v3"
|
||||
messagesEndpoint = "messages"
|
||||
mimeMessagesEndpoint = "messages.mime"
|
||||
addressValidateEndpoint = "address/validate"
|
||||
addressParseEndpoint = "address/parse"
|
||||
bouncesEndpoint = "bounces"
|
||||
statsEndpoint = "stats"
|
||||
domainsEndpoint = "domains"
|
||||
tagsEndpoint = "tags"
|
||||
campaignsEndpoint = "campaigns"
|
||||
eventsEndpoint = "events"
|
||||
credentialsEndpoint = "credentials"
|
||||
unsubscribesEndpoint = "unsubscribes"
|
||||
routesEndpoint = "routes"
|
||||
webhooksEndpoint = "webhooks"
|
||||
listsEndpoint = "lists"
|
||||
basicAuthUser = "api"
|
||||
)
|
||||
|
||||
// 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
|
||||
PublicApiKey() string
|
||||
Client() *http.Client
|
||||
SetClient(client *http.Client)
|
||||
Send(m *Message) (string, string, error)
|
||||
ValidateEmail(email string) (EmailVerification, error)
|
||||
ParseAddresses(addresses ...string) ([]string, []string, error)
|
||||
GetBounces(limit, skip int) (int, []Bounce, error)
|
||||
GetSingleBounce(address string) (Bounce, error)
|
||||
AddBounce(address, code, error string) error
|
||||
DeleteBounce(address string) error
|
||||
GetStats(limit int, skip int, startDate *time.Time, event ...string) (int, []Stat, error)
|
||||
GetTag(tag string) (TagItem, error)
|
||||
DeleteTag(tag string) error
|
||||
ListTags(*TagOptions) *TagIterator
|
||||
GetDomains(limit, skip int) (int, []Domain, error)
|
||||
GetSingleDomain(domain string) (Domain, []DNSRecord, []DNSRecord, error)
|
||||
CreateDomain(name string, smtpPassword string, spamAction string, wildcard bool) error
|
||||
DeleteDomain(name string) error
|
||||
GetCampaigns() (int, []Campaign, error)
|
||||
CreateCampaign(name, id string) error
|
||||
UpdateCampaign(oldId, name, newId string) error
|
||||
DeleteCampaign(id string) error
|
||||
GetComplaints(limit, skip int) (int, []Complaint, error)
|
||||
GetSingleComplaint(address string) (Complaint, error)
|
||||
GetStoredMessage(id string) (StoredMessage, error)
|
||||
GetStoredMessageRaw(id string) (StoredMessageRaw, error)
|
||||
DeleteStoredMessage(id string) error
|
||||
GetCredentials(limit, skip int) (int, []Credential, error)
|
||||
CreateCredential(login, password string) error
|
||||
ChangeCredentialPassword(id, password string) error
|
||||
DeleteCredential(id string) error
|
||||
GetUnsubscribes(limit, skip int) (int, []Unsubscription, error)
|
||||
GetUnsubscribesByAddress(string) (int, []Unsubscription, error)
|
||||
Unsubscribe(address, tag string) error
|
||||
RemoveUnsubscribe(string) error
|
||||
RemoveUnsubscribeWithTag(a, t string) error
|
||||
CreateComplaint(string) error
|
||||
DeleteComplaint(string) error
|
||||
GetRoutes(limit, skip int) (int, []Route, error)
|
||||
GetRouteByID(string) (Route, error)
|
||||
CreateRoute(Route) (Route, error)
|
||||
DeleteRoute(string) error
|
||||
UpdateRoute(string, Route) (Route, error)
|
||||
GetWebhooks() (map[string]string, error)
|
||||
CreateWebhook(kind, url string) error
|
||||
DeleteWebhook(kind string) error
|
||||
GetWebhookByType(kind string) (string, error)
|
||||
UpdateWebhook(kind, url string) error
|
||||
VerifyWebhookRequest(req *http.Request) (verified bool, err error)
|
||||
GetLists(limit, skip int, filter string) (int, []List, error)
|
||||
CreateList(List) (List, error)
|
||||
DeleteList(string) error
|
||||
GetListByAddress(string) (List, error)
|
||||
UpdateList(string, List) (List, error)
|
||||
GetMembers(limit, skip int, subfilter *bool, address string) (int, []Member, error)
|
||||
GetMemberByAddress(MemberAddr, listAddr string) (Member, error)
|
||||
CreateMember(merge bool, addr string, prototype Member) error
|
||||
CreateMemberList(subscribed *bool, addr string, newMembers []interface{}) error
|
||||
UpdateMember(Member, list string, prototype Member) (Member, error)
|
||||
DeleteMember(Member, list string) error
|
||||
NewMessage(from, subject, text string, to ...string) *Message
|
||||
NewMIMEMessage(body io.ReadCloser, to ...string) *Message
|
||||
NewEventIterator() *EventIterator
|
||||
ListEvents(*EventsOptions) *EventIterator
|
||||
PollEvents(*EventsOptions) *EventPoller
|
||||
SetAPIBase(url string)
|
||||
}
|
||||
|
||||
// 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
|
||||
publicApiKey string
|
||||
client *http.Client
|
||||
baseURL string
|
||||
}
|
||||
|
||||
// NewMailGun creates a new client instance.
|
||||
func NewMailgun(domain, apiKey, publicApiKey string) Mailgun {
|
||||
m := MailgunImpl{
|
||||
apiBase: ApiBase,
|
||||
domain: domain,
|
||||
apiKey: apiKey,
|
||||
publicApiKey: publicApiKey,
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
// Return a new Mailgun client using the environment variables
|
||||
// MG_API_KEY, MG_DOMAIN, MG_PUBLIC_API_KEY and MG_URL
|
||||
func NewMailgunFromEnv() (Mailgun, 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 := MailgunImpl{
|
||||
domain: domain,
|
||||
apiKey: apiKey,
|
||||
publicApiKey: os.Getenv("MG_PUBLIC_API_KEY"),
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
url := os.Getenv("MG_URL")
|
||||
if url != "" {
|
||||
mg.SetAPIBase(url)
|
||||
}
|
||||
return &mg, nil
|
||||
}
|
||||
|
||||
// ApiBase returns the API Base URL configured for this client.
|
||||
func (m *MailgunImpl) ApiBase() string {
|
||||
return m.apiBase
|
||||
}
|
||||
|
||||
// Domain returns the domain configured for this client.
|
||||
func (m *MailgunImpl) Domain() string {
|
||||
return m.domain
|
||||
}
|
||||
|
||||
// ApiKey returns the API key configured for this client.
|
||||
func (m *MailgunImpl) ApiKey() string {
|
||||
return m.apiKey
|
||||
}
|
||||
|
||||
// PublicApiKey returns the public API key configured for this client.
|
||||
func (m *MailgunImpl) PublicApiKey() string {
|
||||
return m.publicApiKey
|
||||
}
|
||||
|
||||
// Client returns the HTTP client configured for this client.
|
||||
func (m *MailgunImpl) Client() *http.Client {
|
||||
return m.client
|
||||
}
|
||||
|
||||
// SetClient updates the HTTP client for this client.
|
||||
func (m *MailgunImpl) SetClient(c *http.Client) {
|
||||
m.client = c
|
||||
}
|
||||
|
||||
// SetAPIBase updates the API Base URL for this client.
|
||||
func (m *MailgunImpl) SetAPIBase(address string) {
|
||||
m.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)
|
||||
}
|
||||
|
||||
// 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, id string) string {
|
||||
tail := ""
|
||||
if id != "" {
|
||||
tail = fmt.Sprintf("/%s", id)
|
||||
}
|
||||
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")
|
||||
}
|
69
vendor/gopkg.in/mailgun/mailgun-go.v1/mailgun_test.go
generated
vendored
Normal file
69
vendor/gopkg.in/mailgun/mailgun-go.v1/mailgun_test.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const domain = "valid-mailgun-domain"
|
||||
const apiKey = "valid-mailgun-api-key"
|
||||
const publicApiKey = "valid-mailgun-public-api-key"
|
||||
|
||||
func TestMailgunGinkgo(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Mailgun Test Suite")
|
||||
}
|
||||
|
||||
func TestMailgun(t *testing.T) {
|
||||
m := NewMailgun(domain, apiKey, publicApiKey)
|
||||
|
||||
ensure.DeepEqual(t, m.Domain(), domain)
|
||||
ensure.DeepEqual(t, m.ApiKey(), apiKey)
|
||||
ensure.DeepEqual(t, m.PublicApiKey(), publicApiKey)
|
||||
ensure.DeepEqual(t, m.Client(), http.DefaultClient)
|
||||
|
||||
client := new(http.Client)
|
||||
m.SetClient(client)
|
||||
ensure.DeepEqual(t, client, m.Client())
|
||||
}
|
||||
|
||||
func TestBounceGetCode(t *testing.T) {
|
||||
b1 := &Bounce{
|
||||
CreatedAt: "blah",
|
||||
code: 123,
|
||||
Address: "blort",
|
||||
Error: "bletch",
|
||||
}
|
||||
c, err := b1.GetCode()
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, c, 123)
|
||||
|
||||
b2 := &Bounce{
|
||||
CreatedAt: "blah",
|
||||
code: "456",
|
||||
Address: "blort",
|
||||
Error: "Bletch",
|
||||
}
|
||||
c, err = b2.GetCode()
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, c, 456)
|
||||
|
||||
b3 := &Bounce{
|
||||
CreatedAt: "blah",
|
||||
code: "456H",
|
||||
Address: "blort",
|
||||
Error: "Bletch",
|
||||
}
|
||||
c, err = b3.GetCode()
|
||||
ensure.NotNil(t, err)
|
||||
|
||||
e, ok := err.(*strconv.NumError)
|
||||
if !ok && e != nil {
|
||||
t.Fatal("Expected a syntax error in numeric conversion: got ", err)
|
||||
}
|
||||
}
|
321
vendor/gopkg.in/mailgun/mailgun-go.v1/mailing_lists.go
generated
vendored
Normal file
321
vendor/gopkg.in/mailgun/mailgun-go.v1/mailing_lists.go
generated
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A mailing list may have one of three membership modes.
|
||||
// ReadOnly specifies that nobody, including Members,
|
||||
// may send messages to the mailing list.
|
||||
// Messages distributed on such lists come from list administrator accounts only.
|
||||
// Members specifies that only those who subscribe to the mailing list may send messages.
|
||||
// Everyone specifies that anyone and everyone may both read and submit messages
|
||||
// to the mailing list, including non-subscribers.
|
||||
const (
|
||||
ReadOnly = "readonly"
|
||||
Members = "members"
|
||||
Everyone = "everyone"
|
||||
)
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
// A List structure provides information for a mailing list.
|
||||
//
|
||||
// AccessLevel may be one of ReadOnly, Members, or Everyone.
|
||||
type List struct {
|
||||
Address string `json:"address",omitempty"`
|
||||
Name string `json:"name",omitempty"`
|
||||
Description string `json:"description",omitempty"`
|
||||
AccessLevel string `json:"access_level",omitempty"`
|
||||
CreatedAt string `json:"created_at",omitempty"`
|
||||
MembersCount int `json:"members_count",omitempty"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// GetLists returns the specified set of mailing lists administered by your account.
|
||||
func (mg *MailgunImpl) GetLists(limit, skip int, filter string) (int, []List, error) {
|
||||
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint))
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
p := newUrlEncodedPayload()
|
||||
if limit != DefaultLimit {
|
||||
p.addValue("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if skip != DefaultSkip {
|
||||
p.addValue("skip", strconv.Itoa(skip))
|
||||
}
|
||||
if filter != "" {
|
||||
p.addValue("address", filter)
|
||||
}
|
||||
var envelope struct {
|
||||
Items []List `json:"items"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
response, err := makeRequest(r, "GET", p)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
err = response.parseFromJSON(&envelope)
|
||||
return envelope.TotalCount, envelope.Items, err
|
||||
}
|
||||
|
||||
// CreateList 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) CreateList(prototype List) (List, 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", prototype.AccessLevel)
|
||||
}
|
||||
response, err := makePostRequest(r, p)
|
||||
if err != nil {
|
||||
return List{}, err
|
||||
}
|
||||
var l List
|
||||
err = response.parseFromJSON(&l)
|
||||
return l, err
|
||||
}
|
||||
|
||||
// DeleteList 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) DeleteList(addr string) error {
|
||||
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetListByAddress 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) GetListByAddress(addr string) (List, error) {
|
||||
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
response, err := makeGetRequest(r)
|
||||
if err != nil {
|
||||
return List{}, err
|
||||
}
|
||||
|
||||
var envelope struct {
|
||||
List `json:"list"`
|
||||
}
|
||||
err = response.parseFromJSON(&envelope)
|
||||
return envelope.List, err
|
||||
}
|
||||
|
||||
// UpdateList 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) UpdateList(addr string, prototype List) (List, 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", prototype.AccessLevel)
|
||||
}
|
||||
var l List
|
||||
response, err := makePutRequest(r, p)
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
err = response.parseFromJSON(&l)
|
||||
return l, err
|
||||
}
|
||||
|
||||
// GetMembers returns the list of members belonging to the indicated mailing list.
|
||||
// The s parameter can be set to one of three settings to help narrow the returned data set:
|
||||
// All indicates that you want both Members and unsubscribed members alike, while
|
||||
// Subscribed and Unsubscribed indicate you want only those eponymous subsets.
|
||||
func (mg *MailgunImpl) GetMembers(limit, skip int, s *bool, addr string) (int, []Member, error) {
|
||||
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr))
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
p := newUrlEncodedPayload()
|
||||
if limit != DefaultLimit {
|
||||
p.addValue("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if skip != DefaultSkip {
|
||||
p.addValue("skip", strconv.Itoa(skip))
|
||||
}
|
||||
if s != nil {
|
||||
p.addValue("subscribed", yesNo(*s))
|
||||
}
|
||||
var envelope struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Items []Member `json:"items"`
|
||||
}
|
||||
response, err := makeRequest(r, "GET", p)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
err = response.parseFromJSON(&envelope)
|
||||
return envelope.TotalCount, envelope.Items, err
|
||||
}
|
||||
|
||||
// GetMemberByAddress returns a complete Member structure for a member of a mailing list,
|
||||
// given only their subscription e-mail address.
|
||||
func (mg *MailgunImpl) GetMemberByAddress(s, l string) (Member, error) {
|
||||
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, l) + "/" + s)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
response, err := makeGetRequest(r)
|
||||
if err != nil {
|
||||
return Member{}, err
|
||||
}
|
||||
var envelope struct {
|
||||
Member Member `json:"member"`
|
||||
}
|
||||
err = response.parseFromJSON(&envelope)
|
||||
return envelope.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(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(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(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(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(member, addr string) error {
|
||||
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr) + "/" + member)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
_, err := makeDeleteRequest(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(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
|
||||
}
|
||||
fmt.Println(string(bs))
|
||||
p.addValue("members", string(bs))
|
||||
_, err = makePostRequest(r, p)
|
||||
return err
|
||||
}
|
144
vendor/gopkg.in/mailgun/mailgun-go.v1/mailing_lists_test.go
generated
vendored
Normal file
144
vendor/gopkg.in/mailgun/mailgun-go.v1/mailing_lists_test.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func setup(t *testing.T) (Mailgun, string) {
|
||||
domain := reqEnv(t, "MG_DOMAIN")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
address := fmt.Sprintf("%s@%s", strings.ToLower(randomString(6, "list")), domain)
|
||||
_, err = mg.CreateList(List{
|
||||
Address: address,
|
||||
Name: address,
|
||||
Description: "TestMailingListMembers-related mailing list",
|
||||
AccessLevel: Members,
|
||||
})
|
||||
ensure.Nil(t, err)
|
||||
return mg, address
|
||||
}
|
||||
|
||||
func teardown(t *testing.T, mg Mailgun, address string) {
|
||||
ensure.Nil(t, mg.DeleteList(address))
|
||||
}
|
||||
|
||||
func TestMailingListMembers(t *testing.T) {
|
||||
mg, address := setup(t)
|
||||
defer teardown(t, mg, address)
|
||||
|
||||
var countPeople = func() int {
|
||||
n, _, err := mg.GetMembers(DefaultLimit, DefaultSkip, All, address)
|
||||
ensure.Nil(t, err)
|
||||
return n
|
||||
}
|
||||
|
||||
startCount := countPeople()
|
||||
protoJoe := Member{
|
||||
Address: "joe@example.com",
|
||||
Name: "Joe Example",
|
||||
Subscribed: Subscribed,
|
||||
}
|
||||
ensure.Nil(t, mg.CreateMember(true, address, protoJoe))
|
||||
newCount := countPeople()
|
||||
ensure.False(t, newCount <= startCount)
|
||||
|
||||
theMember, err := mg.GetMemberByAddress("joe@example.com", address)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, theMember.Address, protoJoe.Address)
|
||||
ensure.DeepEqual(t, theMember.Name, protoJoe.Name)
|
||||
ensure.DeepEqual(t, theMember.Subscribed, protoJoe.Subscribed)
|
||||
ensure.True(t, len(theMember.Vars) == 0)
|
||||
|
||||
_, err = mg.UpdateMember("joe@example.com", address, Member{
|
||||
Name: "Joe Cool",
|
||||
})
|
||||
ensure.Nil(t, err)
|
||||
|
||||
theMember, err = mg.GetMemberByAddress("joe@example.com", address)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, theMember.Name, "Joe Cool")
|
||||
ensure.Nil(t, mg.DeleteMember("joe@example.com", address))
|
||||
ensure.DeepEqual(t, countPeople(), startCount)
|
||||
|
||||
err = mg.CreateMemberList(nil, address, []interface{}{
|
||||
Member{
|
||||
Address: "joe.user1@example.com",
|
||||
Name: "Joe's debugging account",
|
||||
Subscribed: Unsubscribed,
|
||||
},
|
||||
Member{
|
||||
Address: "Joe Cool <joe.user2@example.com>",
|
||||
Name: "Joe's Cool Account",
|
||||
Subscribed: Subscribed,
|
||||
},
|
||||
Member{
|
||||
Address: "joe.user3@example.com",
|
||||
Vars: map[string]interface{}{
|
||||
"packet-email": "KW9ABC @ BOGBBS-4.#NCA.CA.USA.NOAM",
|
||||
},
|
||||
},
|
||||
})
|
||||
ensure.Nil(t, err)
|
||||
|
||||
theMember, err = mg.GetMemberByAddress("joe.user2@example.com", address)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, theMember.Name, "Joe's Cool Account")
|
||||
ensure.NotNil(t, theMember.Subscribed)
|
||||
ensure.True(t, *theMember.Subscribed)
|
||||
}
|
||||
|
||||
func TestMailingLists(t *testing.T) {
|
||||
domain := reqEnv(t, "MG_DOMAIN")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
listAddr := fmt.Sprintf("%s@%s", strings.ToLower(randomString(7, "list")), domain)
|
||||
protoList := List{
|
||||
Address: listAddr,
|
||||
Name: "List1",
|
||||
Description: "A list created by an acceptance test.",
|
||||
AccessLevel: Members,
|
||||
}
|
||||
|
||||
var countLists = func() int {
|
||||
total, _, err := mg.GetLists(DefaultLimit, DefaultSkip, "")
|
||||
ensure.Nil(t, err)
|
||||
return total
|
||||
}
|
||||
|
||||
_, err = mg.CreateList(protoList)
|
||||
ensure.Nil(t, err)
|
||||
defer func() {
|
||||
ensure.Nil(t, mg.DeleteList(listAddr))
|
||||
|
||||
_, err := mg.GetListByAddress(listAddr)
|
||||
ensure.NotNil(t, err)
|
||||
}()
|
||||
|
||||
actualCount := countLists()
|
||||
ensure.False(t, actualCount < 1)
|
||||
|
||||
theList, err := mg.GetListByAddress(listAddr)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
protoList.CreatedAt = theList.CreatedAt // ignore this field when comparing.
|
||||
ensure.DeepEqual(t, theList, protoList)
|
||||
|
||||
_, err = mg.UpdateList(listAddr, List{
|
||||
Description: "A list whose description changed",
|
||||
})
|
||||
ensure.Nil(t, err)
|
||||
|
||||
theList, err = mg.GetListByAddress(listAddr)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
newList := protoList
|
||||
newList.Description = "A list whose description changed"
|
||||
ensure.DeepEqual(t, theList, newList)
|
||||
}
|
675
vendor/gopkg.in/mailgun/mailgun-go.v1/messages.go
generated
vendored
Normal file
675
vendor/gopkg.in/mailgun/mailgun-go.v1/messages.go
generated
vendored
Normal file
@@ -0,0 +1,675 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"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
|
||||
|
||||
// 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
|
||||
|
||||
testMode bool
|
||||
tracking bool
|
||||
trackingClicks bool
|
||||
trackingOpens bool
|
||||
headers map[string]string
|
||||
variables map[string]string
|
||||
recipientVariables map[string]map[string]interface{}
|
||||
|
||||
dkimSet bool
|
||||
trackingSet bool
|
||||
trackingClicksSet bool
|
||||
trackingOpensSet bool
|
||||
|
||||
specific features
|
||||
mg Mailgun
|
||||
}
|
||||
|
||||
type ReaderAttachment struct {
|
||||
Filename string
|
||||
ReadCloser io.ReadCloser
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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, and setHTML are invoked via the package-global AddCC, AddBCC,
|
||||
// RecipientCount, and SetHtml 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)
|
||||
addValues(*formDataPayload)
|
||||
isValid() bool
|
||||
endpoint() string
|
||||
recipientCount() int
|
||||
}
|
||||
|
||||
// NewMessage returns a new e-mail message with the simplest envelop needed to send.
|
||||
//
|
||||
// DEPRECATED.
|
||||
// The package will panic if you use AddRecipient(), AddBcc(), AddCc(), et. al.
|
||||
// on a message already equipped with MaxNumberOfRecipients recipients.
|
||||
// Use Mailgun.NewMessage() instead.
|
||||
// It works similarly to this function, but supports larger lists of recipients.
|
||||
func NewMessage(from string, subject string, text string, to ...string) *Message {
|
||||
return &Message{
|
||||
specific: &plainMessage{
|
||||
from: from,
|
||||
subject: subject,
|
||||
text: text,
|
||||
},
|
||||
to: to,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// DEPRECATED.
|
||||
// The package will panic if you use AddRecipient(), AddBcc(), AddCc(), et. al.
|
||||
// on a message already equipped with MaxNumberOfRecipients recipients.
|
||||
// Use Mailgun.NewMIMEMessage() instead.
|
||||
// It works similarly to this function, but supports larger lists of recipients.
|
||||
func NewMIMEMessage(body io.ReadCloser, to ...string) *Message {
|
||||
return &Message{
|
||||
specific: &mimeMessage{
|
||||
body: body,
|
||||
},
|
||||
to: to,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// NOTE: Above a certain limit (currently 1000 recipients),
|
||||
// this function will cause the message as it's currently defined to be sent.
|
||||
// This allows you to support large mailing lists without running into Mailgun's API limitations.
|
||||
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.
|
||||
//
|
||||
// NOTE: Above a certain limit (see MaxNumberOfRecipients),
|
||||
// this function will cause the message as it's currently defined to be sent.
|
||||
// This allows you to support large mailing lists without running into Mailgun's API limitations.
|
||||
func (m *Message) AddRecipientAndVariables(r string, vars map[string]interface{}) error {
|
||||
if m.RecipientCount() >= MaxNumberOfRecipients {
|
||||
_, _, err := m.send()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.to = make([]string, len(m.to))
|
||||
m.recipientVariables = make(map[string]map[string]interface{}, len(m.recipientVariables))
|
||||
}
|
||||
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() (string, string, error) {
|
||||
return m.mg.Send(m)
|
||||
}
|
||||
|
||||
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) {}
|
||||
|
||||
// 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) {}
|
||||
|
||||
// AddTag attaches a tag 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) {
|
||||
m.tags = append(m.tags, tag)
|
||||
}
|
||||
|
||||
// This feature 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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
pdt := new(time.Time)
|
||||
*pdt = dt
|
||||
m.deliveryTime = pdt
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Refer to the Mailgun documentation for more information.
|
||||
func (m *Message) SetTrackingClicks(trackingClicks bool) {
|
||||
m.trackingClicks = trackingClicks
|
||||
m.trackingClicksSet = true
|
||||
}
|
||||
|
||||
// Refer to the Mailgun documentation for more information.
|
||||
func (m *Message) SetTrackingOpens(trackingOpens bool) {
|
||||
m.trackingOpens = trackingOpens
|
||||
m.trackingOpensSet = true
|
||||
}
|
||||
|
||||
// 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 {
|
||||
j, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.variables == nil {
|
||||
m.variables = make(map[string]string)
|
||||
}
|
||||
m.variables[variable] = string(j)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 (m *MailgunImpl) Send(message *Message) (mes string, id string, err error) {
|
||||
if !isValid(message) {
|
||||
err = errors.New("Message not valid")
|
||||
} else {
|
||||
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 != nil {
|
||||
payload.addValue("o:deliverytime", formatMailgunTime(message.deliveryTime))
|
||||
}
|
||||
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.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.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.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)
|
||||
}
|
||||
}
|
||||
|
||||
r := newHTTPRequest(generateApiUrl(m, message.specific.endpoint()))
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
var response sendMessageResponse
|
||||
err = postResponseFromJSON(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)
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
} else {
|
||||
return "no"
|
||||
}
|
||||
}
|
||||
|
||||
// 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 !validateStringList(m.to, true) {
|
||||
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.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(id string) (StoredMessage, error) {
|
||||
url := generateStoredMessageUrl(mg, messagesEndpoint, id)
|
||||
r := newHTTPRequest(url)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
|
||||
var response StoredMessage
|
||||
err := getResponseFromJSON(r, &response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// 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(id string) (StoredMessageRaw, error) {
|
||||
url := generateStoredMessageUrl(mg, messagesEndpoint, id)
|
||||
r := newHTTPRequest(url)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
r.addHeader("Accept", "message/rfc2822")
|
||||
|
||||
var response StoredMessageRaw
|
||||
err := getResponseFromJSON(r, &response)
|
||||
return response, err
|
||||
|
||||
}
|
||||
|
||||
// DeleteStoredMessage removes a previously stored message.
|
||||
// Note that Mailgun institutes a policy of automatically deleting messages after a set time.
|
||||
// Consult the current Mailgun API documentation for more details.
|
||||
func (mg *MailgunImpl) DeleteStoredMessage(id string) error {
|
||||
url := generateStoredMessageUrl(mg, messagesEndpoint, id)
|
||||
r := newHTTPRequest(url)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
349
vendor/gopkg.in/mailgun/mailgun-go.v1/messages_test.go
generated
vendored
Normal file
349
vendor/gopkg.in/mailgun/mailgun-go.v1/messages_test.go
generated
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
const (
|
||||
fromUser = "=?utf-8?q?Katie_Brewer=2C_CFP=C2=AE?= <joe@example.com>"
|
||||
exampleSubject = "Mailgun-go Example Subject"
|
||||
exampleText = "Testing some Mailgun awesomeness!"
|
||||
exampleHtml = "<html><head /><body><p>Testing some <a href=\"http://google.com?q=abc&r=def&s=ghi\">Mailgun HTML awesomeness!</a> at www.kc5tja@yahoo.com</p></body></html>"
|
||||
|
||||
exampleMime = `Content-Type: text/plain; charset="ascii"
|
||||
Subject: Joe's Example Subject
|
||||
From: Joe Example <joe@example.com>
|
||||
To: BARGLEGARF <sam.falvo@rackspace.com>
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Date: Thu, 6 Mar 2014 00:37:52 +0000
|
||||
|
||||
Testing some Mailgun MIME awesomeness!
|
||||
`
|
||||
templateText = "Greetings %recipient.name%! Your reserved seat is at table %recipient.table%."
|
||||
)
|
||||
|
||||
func TestSendLegacyPlain(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendPlain:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendLegacyPlainWithTracking(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
m.SetTracking(true)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendPlainWithTracking:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendLegacyPlainAt(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
m.SetDeliveryTime(time.Now().Add(5 * time.Minute))
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendPlainAt:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendLegacyHtml(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
m.SetHtml(exampleHtml)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendHtml:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendLegacyTracking(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := NewMessage(fromUser, exampleSubject, exampleText+"Tracking!\n", toUser)
|
||||
m.SetTracking(false)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendTracking:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendLegacyTag(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := NewMessage(fromUser, exampleSubject, exampleText+"Tags Galore!\n", toUser)
|
||||
m.AddTag("FooTag")
|
||||
m.AddTag("BarTag")
|
||||
m.AddTag("BlortTag")
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendTag:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendLegacyMIME(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := NewMIMEMessage(ioutil.NopCloser(strings.NewReader(exampleMime)), toUser)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendMIME:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetStoredMessage(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
id, err := findStoredMessageID(mg) // somehow...
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
// First, get our stored message.
|
||||
msg, err := mg.GetStoredMessage(id)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
fields := map[string]string{
|
||||
" From": msg.From,
|
||||
" Sender": msg.Sender,
|
||||
" Subject": msg.Subject,
|
||||
"Attachments": fmt.Sprintf("%d", len(msg.Attachments)),
|
||||
" Headers": fmt.Sprintf("%d", len(msg.MessageHeaders)),
|
||||
}
|
||||
for k, v := range fields {
|
||||
fmt.Printf("%13s: %s\n", k, v)
|
||||
}
|
||||
|
||||
// We're done with it; now delete it.
|
||||
ensure.Nil(t, mg.DeleteStoredMessage(id))
|
||||
})
|
||||
}
|
||||
|
||||
// Tries to locate the first stored event type, returning the associated stored message key.
|
||||
func findStoredMessageID(mg Mailgun) (string, error) {
|
||||
ei := mg.NewEventIterator()
|
||||
err := ei.GetFirstPage(GetEventsOptions{})
|
||||
for {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(ei.Events()) == 0 {
|
||||
break
|
||||
}
|
||||
for _, event := range ei.Events() {
|
||||
if event["event"] == "stored" {
|
||||
s := event["storage"].(map[string]interface{})
|
||||
k := s["key"]
|
||||
return k.(string), nil
|
||||
}
|
||||
}
|
||||
err = ei.GetNext()
|
||||
}
|
||||
return "", fmt.Errorf("No stored messages found. Try changing MG_EMAIL_TO to an address that stores messages and try again.")
|
||||
}
|
||||
|
||||
func TestSendMGPlain(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendPlain:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMGPlainWithTracking(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
m.SetTracking(true)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendPlainWithTracking:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMGPlainAt(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
m.SetDeliveryTime(time.Now().Add(5 * time.Minute))
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendPlainAt:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMGHtml(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
m.SetHtml(exampleHtml)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendHtml:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMGTracking(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := mg.NewMessage(fromUser, exampleSubject, exampleText+"Tracking!\n", toUser)
|
||||
m.SetTracking(false)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendTracking:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMGTag(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := mg.NewMessage(fromUser, exampleSubject, exampleText+"Tags Galore!\n", toUser)
|
||||
m.AddTag("FooTag")
|
||||
m.AddTag("BarTag")
|
||||
m.AddTag("BlortTag")
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendTag:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMGMIME(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := mg.NewMIMEMessage(ioutil.NopCloser(strings.NewReader(exampleMime)), toUser)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
t.Log("TestSendMIME:MSG(" + msg + "),ID(" + id + ")")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMGBatchFailRecipients(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := mg.NewMessage(fromUser, exampleSubject, exampleText+"Batch\n")
|
||||
for i := 0; i < MaxNumberOfRecipients; i++ {
|
||||
m.AddRecipient("") // We expect this to indicate a failure at the API
|
||||
}
|
||||
err = m.AddRecipientAndVariables(toUser, nil)
|
||||
// In case of error the SDK didn't send the message,
|
||||
// OR the API didn't check for empty To: headers.
|
||||
ensure.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMGBatchRecipientVariables(t *testing.T) {
|
||||
spendMoney(t, func() {
|
||||
toUser := reqEnv(t, "MG_EMAIL_TO")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
m := mg.NewMessage(fromUser, exampleSubject, templateText)
|
||||
err = m.AddRecipientAndVariables(toUser, map[string]interface{}{
|
||||
"name": "Joe Cool Example",
|
||||
"table": 42,
|
||||
})
|
||||
ensure.Nil(t, err)
|
||||
_, _, err = mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMGOffline(t *testing.T) {
|
||||
const (
|
||||
exampleDomain = "testDomain"
|
||||
exampleAPIKey = "testAPIKey"
|
||||
examplePublicAPIKey = "testPublicAPIKey"
|
||||
toUser = "test@test.com"
|
||||
exampleMessage = "Queue. Thank you"
|
||||
exampleID = "<20111114174239.25659.5817@samples.mailgun.org>"
|
||||
)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ensure.DeepEqual(t, req.Method, http.MethodPost)
|
||||
ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/%s/messages", exampleDomain))
|
||||
values, err := parseContentType(req)
|
||||
ensure.Nil(t, err)
|
||||
ensure.True(t, len(values) != 0)
|
||||
ensure.DeepEqual(t, values.Get("from"), fromUser)
|
||||
ensure.DeepEqual(t, values.Get("subject"), exampleSubject)
|
||||
ensure.DeepEqual(t, values.Get("text"), exampleText)
|
||||
ensure.DeepEqual(t, values.Get("to"), toUser)
|
||||
rsp := fmt.Sprintf(`{"message":"%s", "id":"%s"}`, exampleMessage, exampleID)
|
||||
fmt.Fprint(w, rsp)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
mg := NewMailgun(exampleDomain, exampleAPIKey, examplePublicAPIKey)
|
||||
mg.SetAPIBase(srv.URL)
|
||||
|
||||
m := NewMessage(fromUser, exampleSubject, exampleText, toUser)
|
||||
msg, id, err := mg.Send(m)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, msg, exampleMessage)
|
||||
ensure.DeepEqual(t, id, exampleID)
|
||||
}
|
162
vendor/gopkg.in/mailgun/mailgun-go.v1/rest_shim.go
generated
vendored
Normal file
162
vendor/gopkg.in/mailgun/mailgun-go.v1/rest_shim.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"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/1.0.0"
|
||||
|
||||
// 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(r *httpRequest, kind string, p payload) (*httpResponse, error) {
|
||||
r.addHeader("User-Agent", MailgunGoUserAgent)
|
||||
rsp, err := r.makeRequest(kind, 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(r *httpRequest, v interface{}) error {
|
||||
r.addHeader("User-Agent", MailgunGoUserAgent)
|
||||
response, err := r.makeGetRequest()
|
||||
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(r *httpRequest, p payload, v interface{}) error {
|
||||
r.addHeader("User-Agent", MailgunGoUserAgent)
|
||||
response, err := r.makePostRequest(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(r *httpRequest, p payload, v interface{}) error {
|
||||
r.addHeader("User-Agent", MailgunGoUserAgent)
|
||||
response, err := r.makePutRequest(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(r *httpRequest) (*httpResponse, error) {
|
||||
r.addHeader("User-Agent", MailgunGoUserAgent)
|
||||
rsp, err := r.makeGetRequest()
|
||||
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(r *httpRequest, p payload) (*httpResponse, error) {
|
||||
r.addHeader("User-Agent", MailgunGoUserAgent)
|
||||
rsp, err := r.makePostRequest(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(r *httpRequest, p payload) (*httpResponse, error) {
|
||||
r.addHeader("User-Agent", MailgunGoUserAgent)
|
||||
rsp, err := r.makePutRequest(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(r *httpRequest) (*httpResponse, error) {
|
||||
r.addHeader("User-Agent", MailgunGoUserAgent)
|
||||
rsp, err := r.makeDeleteRequest()
|
||||
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
|
||||
}
|
136
vendor/gopkg.in/mailgun/mailgun-go.v1/routes.go
generated
vendored
Normal file
136
vendor/gopkg.in/mailgun/mailgun-go.v1/routes.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A Route structure contains information on a configured or to-be-configured route.
|
||||
// The Priority field indicates how soon the route works relative to other configured routes.
|
||||
// Routes of equal priority are consulted in chronological order.
|
||||
// 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.
|
||||
// The Expression field lets you specify a pattern to match incoming messages against.
|
||||
// The Actions field contains strings specifying what to do
|
||||
// with any message which matches the provided expression.
|
||||
// The CreatedAt field provides a time-stamp for when the route came into existence.
|
||||
// Finally, the ID field provides a unique identifier for this 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 {
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Expression string `json:"expression,omitempty"`
|
||||
Actions []string `json:"actions,omitempty"`
|
||||
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
// GetRoutes returns the complete set of routes configured for your domain.
|
||||
// You use routes to configure how to handle returned messages, or
|
||||
// messages sent to a specfic address on your domain.
|
||||
// See the Mailgun documentation for more information.
|
||||
func (mg *MailgunImpl) GetRoutes(limit, skip int) (int, []Route, error) {
|
||||
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint))
|
||||
if limit != DefaultLimit {
|
||||
r.addParameter("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if skip != DefaultSkip {
|
||||
r.addParameter("skip", strconv.Itoa(skip))
|
||||
}
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
var envelope struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Items []Route `json:"items"`
|
||||
}
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
if err != nil {
|
||||
fmt.Printf("err here\n")
|
||||
return -1, nil, err
|
||||
}
|
||||
return envelope.TotalCount, envelope.Items, nil
|
||||
}
|
||||
|
||||
// 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(prototype Route) (Route, 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 envelope struct {
|
||||
Message string `json:"message"`
|
||||
*Route `json:"route"`
|
||||
}
|
||||
err := postResponseFromJSON(r, p, &envelope)
|
||||
return *envelope.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(id string) error {
|
||||
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint) + "/" + id)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetRouteByID retrieves the complete route definition associated with the unique route ID.
|
||||
func (mg *MailgunImpl) GetRouteByID(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(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(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(r, p, &envelope)
|
||||
return envelope, err
|
||||
}
|
52
vendor/gopkg.in/mailgun/mailgun-go.v1/routes_test.go
generated
vendored
Normal file
52
vendor/gopkg.in/mailgun/mailgun-go.v1/routes_test.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func TestRouteCRUD(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
var countRoutes = func() int {
|
||||
count, _, err := mg.GetRoutes(DefaultLimit, DefaultSkip)
|
||||
ensure.Nil(t, err)
|
||||
return count
|
||||
}
|
||||
|
||||
routeCount := countRoutes()
|
||||
|
||||
newRoute, err := mg.CreateRoute(Route{
|
||||
Priority: 1,
|
||||
Description: "Sample Route",
|
||||
Expression: "match_recipient(\".*@samples.mailgun.org\")",
|
||||
Actions: []string{
|
||||
"forward(\"http://example.com/messages/\")",
|
||||
"stop()",
|
||||
},
|
||||
})
|
||||
ensure.Nil(t, err)
|
||||
ensure.True(t, newRoute.ID != "")
|
||||
|
||||
defer func() {
|
||||
ensure.Nil(t, mg.DeleteRoute(newRoute.ID))
|
||||
_, err = mg.GetRouteByID(newRoute.ID)
|
||||
ensure.NotNil(t, err)
|
||||
}()
|
||||
|
||||
newCount := countRoutes()
|
||||
ensure.False(t, newCount <= routeCount)
|
||||
|
||||
theRoute, err := mg.GetRouteByID(newRoute.ID)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, newRoute, theRoute)
|
||||
|
||||
changedRoute, err := mg.UpdateRoute(newRoute.ID, Route{
|
||||
Priority: 2,
|
||||
})
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, changedRoute.Priority, 2)
|
||||
ensure.DeepEqual(t, len(changedRoute.Actions), 2)
|
||||
}
|
81
vendor/gopkg.in/mailgun/mailgun-go.v1/spam_complaints.go
generated
vendored
Normal file
81
vendor/gopkg.in/mailgun/mailgun-go.v1/spam_complaints.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
complaintsEndpoint = "complaints"
|
||||
)
|
||||
|
||||
// Complaint structures track how many times one of your emails have been marked as spam.
|
||||
// CreatedAt indicates when the first report arrives from a given recipient, identified by Address.
|
||||
// Count provides a running counter of how many times
|
||||
// the recipient thought your messages were not solicited.
|
||||
type Complaint struct {
|
||||
Count int `json:"count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type complaintsEnvelope struct {
|
||||
Items []Complaint `json:"items"`
|
||||
Paging Paging `json:"paging"`
|
||||
}
|
||||
|
||||
// GetComplaints 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 (m *MailgunImpl) GetComplaints(limit, skip int) (int, []Complaint, error) {
|
||||
r := newHTTPRequest(generateApiUrl(m, complaintsEndpoint))
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
if limit != -1 {
|
||||
r.addParameter("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if skip != -1 {
|
||||
r.addParameter("skip", strconv.Itoa(skip))
|
||||
}
|
||||
|
||||
var envelope complaintsEnvelope
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
return len(envelope.Items), envelope.Items, nil
|
||||
}
|
||||
|
||||
// GetSingleComplaint 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 (m *MailgunImpl) GetSingleComplaint(address string) (Complaint, error) {
|
||||
r := newHTTPRequest(generateApiUrl(m, complaintsEndpoint) + "/" + address)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
var c Complaint
|
||||
err := getResponseFromJSON(r, &c)
|
||||
return c, err
|
||||
}
|
||||
|
||||
// CreateComplaint registers the specified address as a recipient who has complained of receiving spam
|
||||
// from your domain.
|
||||
func (m *MailgunImpl) CreateComplaint(address string) error {
|
||||
r := newHTTPRequest(generateApiUrl(m, complaintsEndpoint))
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
p := newUrlEncodedPayload()
|
||||
p.addValue("address", address)
|
||||
_, err := makePostRequest(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 (m *MailgunImpl) DeleteComplaint(address string) error {
|
||||
r := newHTTPRequest(generateApiUrl(m, complaintsEndpoint) + "/" + address)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
59
vendor/gopkg.in/mailgun/mailgun-go.v1/spam_complaints_test.go
generated
vendored
Normal file
59
vendor/gopkg.in/mailgun/mailgun-go.v1/spam_complaints_test.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func TestGetComplaints(t *testing.T) {
|
||||
reqEnv(t, "MG_PUBLIC_API_KEY")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
n, complaints, err := mg.GetComplaints(-1, -1)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, len(complaints), n)
|
||||
}
|
||||
|
||||
func TestGetComplaintFromRandomNoComplaint(t *testing.T) {
|
||||
reqEnv(t, "MG_PUBLIC_API_KEY")
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
_, err = mg.GetSingleComplaint(randomString(64, "") + "@example.com")
|
||||
ensure.NotNil(t, err)
|
||||
|
||||
ure, ok := err.(*UnexpectedResponseError)
|
||||
ensure.True(t, ok)
|
||||
ensure.DeepEqual(t, ure.Actual, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestCreateDeleteComplaint(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
var hasComplaint = func(email string) bool {
|
||||
t.Logf("hasComplaint: %s\n", email)
|
||||
_, complaints, err := mg.GetComplaints(DefaultLimit, DefaultSkip)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
for _, complaint := range complaints {
|
||||
t.Logf("Complaint Address: %s\n", complaint.Address)
|
||||
if complaint.Address == email {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
randomMail := strings.ToLower(randomString(64, "")) + "@example.com"
|
||||
ensure.False(t, hasComplaint(randomMail))
|
||||
|
||||
ensure.Nil(t, mg.CreateComplaint(randomMail))
|
||||
ensure.True(t, hasComplaint(randomMail))
|
||||
ensure.Nil(t, mg.DeleteComplaint(randomMail))
|
||||
ensure.False(t, hasComplaint(randomMail))
|
||||
}
|
53
vendor/gopkg.in/mailgun/mailgun-go.v1/stats.go
generated
vendored
Normal file
53
vendor/gopkg.in/mailgun/mailgun-go.v1/stats.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const iso8601date = "2006-01-02"
|
||||
|
||||
type Stat struct {
|
||||
Event string `json:"event"`
|
||||
TotalCount int `json:"total_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Id string `json:"id"`
|
||||
Tags map[string]int `json:"tags"`
|
||||
}
|
||||
|
||||
type statsEnvelope struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Items []Stat `json:"items"`
|
||||
}
|
||||
|
||||
// GetStats returns a basic set of statistics for different events.
|
||||
// Events start at the given start date, if one is provided.
|
||||
// If not, this function will consider all stated events dating to the creation of the sending domain.
|
||||
func (m *MailgunImpl) GetStats(limit int, skip int, startDate *time.Time, event ...string) (int, []Stat, error) {
|
||||
r := newHTTPRequest(generateApiUrl(m, statsEndpoint))
|
||||
|
||||
if limit != -1 {
|
||||
r.addParameter("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if skip != -1 {
|
||||
r.addParameter("skip", strconv.Itoa(skip))
|
||||
}
|
||||
|
||||
if startDate != nil {
|
||||
r.addParameter("start-date", startDate.Format(iso8601date))
|
||||
}
|
||||
|
||||
for _, e := range event {
|
||||
r.addParameter("event", e)
|
||||
}
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
|
||||
var res statsEnvelope
|
||||
err := getResponseFromJSON(r, &res)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
} else {
|
||||
return res.TotalCount, res.Items, nil
|
||||
}
|
||||
}
|
27
vendor/gopkg.in/mailgun/mailgun-go.v1/stats_test.go
generated
vendored
Normal file
27
vendor/gopkg.in/mailgun/mailgun-go.v1/stats_test.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func TestGetStats(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
totalCount, stats, err := mg.GetStats(-1, -1, nil, "sent", "opened")
|
||||
ensure.Nil(t, err)
|
||||
|
||||
t.Logf("Total Count: %d\n", totalCount)
|
||||
t.Logf("Id\tEvent\tCreatedAt\tTotalCount\t\n")
|
||||
for _, stat := range stats {
|
||||
t.Logf("%s\t%s\t%s\t%d\t\n", stat.Id, stat.Event, stat.CreatedAt, stat.TotalCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteTag(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
ensure.Nil(t, mg.DeleteTag("newsletter"))
|
||||
}
|
177
vendor/gopkg.in/mailgun/mailgun-go.v1/tags.go
generated
vendored
Normal file
177
vendor/gopkg.in/mailgun/mailgun-go.v1/tags.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TagItem struct {
|
||||
Value string `json:"tag"`
|
||||
Description string `json:"description"`
|
||||
FirstSeen *time.Time `json:"first-seen,omitempty"`
|
||||
LastSeen *time.Time `json:"last-seen,omitempty"`
|
||||
}
|
||||
|
||||
type TagsPage struct {
|
||||
Items []TagItem `json:"items"`
|
||||
Paging Paging `json:"paging"`
|
||||
}
|
||||
|
||||
type TagOptions struct {
|
||||
// Restrict the page size to this limit
|
||||
Limit int
|
||||
// Return only the tags starting with the given prefix
|
||||
Prefix string
|
||||
// The page direction based off the 'tag' parameter; valid choices are (first, last, next, prev)
|
||||
Page string
|
||||
// The tag that marks piviot point for the 'page' parameter
|
||||
Tag string
|
||||
}
|
||||
|
||||
// DeleteTag removes all counters for a particular tag, including the tag itself.
|
||||
func (m *MailgunImpl) DeleteTag(tag string) error {
|
||||
r := newHTTPRequest(generateApiUrl(m, tagsEndpoint) + "/" + tag)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTag retrieves metadata about the tag from the api
|
||||
func (m *MailgunImpl) GetTag(tag string) (TagItem, error) {
|
||||
r := newHTTPRequest(generateApiUrl(m, tagsEndpoint) + "/" + tag)
|
||||
r.setClient(m.Client())
|
||||
r.setBasicAuth(basicAuthUser, m.ApiKey())
|
||||
var tagItem TagItem
|
||||
return tagItem, getResponseFromJSON(r, &tagItem)
|
||||
}
|
||||
|
||||
// ListTags returns a cursor used to iterate through a list of tags
|
||||
// it := mg.ListTags(nil)
|
||||
// var page TagsPage
|
||||
// for it.Next(&page) {
|
||||
// for _, tag := range(page.Items) {
|
||||
// // Do stuff with tags
|
||||
// }
|
||||
// }
|
||||
// if it.Err() != nil {
|
||||
// log.Fatal(it.Err())
|
||||
// }
|
||||
func (m *MailgunImpl) ListTags(opts *TagOptions) *TagIterator {
|
||||
req := newHTTPRequest(generateApiUrl(m, tagsEndpoint))
|
||||
if opts != nil {
|
||||
if opts.Limit != 0 {
|
||||
req.addParameter("limit", strconv.Itoa(opts.Limit))
|
||||
}
|
||||
if opts.Prefix != "" {
|
||||
req.addParameter("prefix", opts.Prefix)
|
||||
}
|
||||
if opts.Page != "" {
|
||||
req.addParameter("page", opts.Page)
|
||||
}
|
||||
if opts.Tag != "" {
|
||||
req.addParameter("tag", opts.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
initialUrl, _ := req.generateUrlWithParameters()
|
||||
tagPage := TagsPage{
|
||||
Paging: Paging{
|
||||
First: initialUrl,
|
||||
Next: initialUrl,
|
||||
Last: initialUrl,
|
||||
Previous: initialUrl,
|
||||
},
|
||||
}
|
||||
return NewTagCursor(tagPage, m)
|
||||
}
|
||||
|
||||
type TagIterator struct {
|
||||
mg Mailgun
|
||||
curr TagsPage
|
||||
err error
|
||||
}
|
||||
|
||||
// Creates a new cursor from a taglist
|
||||
func NewTagCursor(tagPage TagsPage, mailgun Mailgun) *TagIterator {
|
||||
return &TagIterator{curr: tagPage, mg: mailgun}
|
||||
}
|
||||
|
||||
// Returns the next page in the list of tags
|
||||
func (t *TagIterator) Next(tagPage *TagsPage) bool {
|
||||
if !canFetchPage(t.curr.Paging.Next) {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := t.cursorRequest(tagPage, t.curr.Paging.Next); err != nil {
|
||||
t.err = err
|
||||
return false
|
||||
}
|
||||
t.curr = *tagPage
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns the previous page in the list of tags
|
||||
func (t *TagIterator) Previous(tagPage *TagsPage) bool {
|
||||
if !canFetchPage(t.curr.Paging.Previous) {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := t.cursorRequest(tagPage, t.curr.Paging.Previous); err != nil {
|
||||
t.err = err
|
||||
return false
|
||||
}
|
||||
t.curr = *tagPage
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns the first page in the list of tags
|
||||
func (t *TagIterator) First(tagPage *TagsPage) bool {
|
||||
if err := t.cursorRequest(tagPage, t.curr.Paging.First); err != nil {
|
||||
t.err = err
|
||||
return false
|
||||
}
|
||||
t.curr = *tagPage
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns the last page in the list of tags
|
||||
func (t *TagIterator) Last(tagPage *TagsPage) bool {
|
||||
if err := t.cursorRequest(tagPage, t.curr.Paging.Last); err != nil {
|
||||
t.err = err
|
||||
return false
|
||||
}
|
||||
t.curr = *tagPage
|
||||
return true
|
||||
}
|
||||
|
||||
// Return any error if one occurred
|
||||
func (t *TagIterator) Err() error {
|
||||
return t.err
|
||||
}
|
||||
|
||||
func (t *TagIterator) cursorRequest(tagPage *TagsPage, url string) error {
|
||||
req := newHTTPRequest(url)
|
||||
req.setClient(t.mg.Client())
|
||||
req.setBasicAuth(basicAuthUser, t.mg.ApiKey())
|
||||
return getResponseFromJSON(req, tagPage)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
116
vendor/gopkg.in/mailgun/mailgun-go.v1/tags_test.go
generated
vendored
Normal file
116
vendor/gopkg.in/mailgun/mailgun-go.v1/tags_test.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"fmt"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ = Describe("/v3/{domain}/tags", func() {
|
||||
log := log.New(GinkgoWriter, "tags_test - ", 0)
|
||||
var t GinkgoTInterface
|
||||
var mg Mailgun
|
||||
var err error
|
||||
|
||||
BeforeSuite(func() {
|
||||
mg, err = NewMailgunFromEnv()
|
||||
msg := mg.NewMessage(fromUser, exampleSubject, exampleText, reqEnv(t, "MG_EMAIL_TO"))
|
||||
msg.AddTag("newsletter")
|
||||
msg.AddTag("homer")
|
||||
msg.AddTag("bart")
|
||||
msg.AddTag("disco-steve")
|
||||
msg.AddTag("newsletter")
|
||||
// Create an email with some tags attached
|
||||
_, _, err := mg.Send(msg)
|
||||
if err != nil {
|
||||
Fail(fmt.Sprintf("Mesage send: '%s'", err.Error()))
|
||||
}
|
||||
// Wait for the tag to show up
|
||||
if err := waitForTag(mg, "newsletter"); err != nil {
|
||||
Fail(fmt.Sprintf("While waiting for message: '%s'", err.Error()))
|
||||
}
|
||||
})
|
||||
|
||||
BeforeEach(func() {
|
||||
t = GinkgoT()
|
||||
mg, err = NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
})
|
||||
|
||||
Describe("ListTags()", func() {
|
||||
Context("When a limit parameter of -1 is supplied", func() {
|
||||
It("Should return a list of available tags", func() {
|
||||
it := mg.ListTags(nil)
|
||||
var page TagsPage
|
||||
for it.Next(&page) {
|
||||
Expect(len(page.Items)).NotTo(Equal(0))
|
||||
log.Printf("Tags: %+v\n", page)
|
||||
}
|
||||
ensure.Nil(t, it.Err())
|
||||
})
|
||||
})
|
||||
Context("When limit parameter is supplied", func() {
|
||||
It("Should return a limited list of available tags", func() {
|
||||
cursor := mg.ListTags(&TagOptions{Limit: 1})
|
||||
|
||||
var tags TagsPage
|
||||
for cursor.Next(&tags) {
|
||||
ensure.DeepEqual(t, len(tags.Items), 1)
|
||||
log.Printf("Tags: %+v\n", tags.Items)
|
||||
}
|
||||
ensure.Nil(t, cursor.Err())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("DeleteTag()", func() {
|
||||
Context("When deleting an existing tag", func() {
|
||||
It("Should not error", func() {
|
||||
err = mg.DeleteTag("newsletter")
|
||||
ensure.Nil(t, err)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetTag()", func() {
|
||||
Context("When requesting an existing tag", func() {
|
||||
It("Should not error", func() {
|
||||
tag, err := mg.GetTag("homer")
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, tag.Value, "homer")
|
||||
})
|
||||
})
|
||||
Context("When requesting an non-existant tag", func() {
|
||||
It("Should return error", func() {
|
||||
_, err := mg.GetTag("i-dont-exist")
|
||||
ensure.NotNil(t, err)
|
||||
ensure.DeepEqual(t, GetStatusFromErr(err), 404)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func waitForTag(mg Mailgun, tag string) error {
|
||||
var attempts int
|
||||
for attempts <= 5 {
|
||||
_, err := mg.GetTag(tag)
|
||||
if err != nil {
|
||||
if GetStatusFromErr(err) == 404 {
|
||||
time.Sleep(time.Second * 2)
|
||||
attempts += 1
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
return errors.Errorf("Waited to long for tag '%s' to show up", tag)
|
||||
}
|
81
vendor/gopkg.in/mailgun/mailgun-go.v1/unsubscribes.go
generated
vendored
Normal file
81
vendor/gopkg.in/mailgun/mailgun-go.v1/unsubscribes.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Unsubscription struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
Tags []string `json:"tags"`
|
||||
ID string `json:"id"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// GetUnsubscribes retrieves a list of unsubscriptions issued by recipients of mail from your domain.
|
||||
// Zero is a valid list length.
|
||||
func (mg *MailgunImpl) GetUnsubscribes(limit, skip int) (int, []Unsubscription, error) {
|
||||
r := newHTTPRequest(generateApiUrl(mg, unsubscribesEndpoint))
|
||||
if limit != DefaultLimit {
|
||||
r.addParameter("limit", strconv.Itoa(limit))
|
||||
}
|
||||
if skip != DefaultSkip {
|
||||
r.addParameter("skip", strconv.Itoa(skip))
|
||||
}
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
var envelope struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Items []Unsubscription `json:"items"`
|
||||
}
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
return envelope.TotalCount, envelope.Items, err
|
||||
}
|
||||
|
||||
// GetUnsubscribesByAddress retrieves a list of unsubscriptions by recipient address.
|
||||
// Zero is a valid list length.
|
||||
func (mg *MailgunImpl) GetUnsubscribesByAddress(a string) (int, []Unsubscription, error) {
|
||||
r := newHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, a))
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
var envelope struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Items []Unsubscription `json:"items"`
|
||||
}
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
return envelope.TotalCount, envelope.Items, err
|
||||
}
|
||||
|
||||
// Unsubscribe adds an e-mail address to the domain's unsubscription table.
|
||||
func (mg *MailgunImpl) Unsubscribe(a, t string) error {
|
||||
r := newHTTPRequest(generateApiUrl(mg, unsubscribesEndpoint))
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
p := newUrlEncodedPayload()
|
||||
p.addValue("address", a)
|
||||
p.addValue("tag", t)
|
||||
_, err := makePostRequest(r, p)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveUnsubscribe removes the e-mail address given from the domain's unsubscription table.
|
||||
// If passing in an ID (discoverable from, e.g., GetUnsubscribes()), the e-mail address associated
|
||||
// with the given ID will be removed.
|
||||
func (mg *MailgunImpl) RemoveUnsubscribe(a string) error {
|
||||
r := newHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, a))
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveUnsubscribe 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., GetUnsubscribes()), the e-mail address associated
|
||||
// with the given ID will be removed.
|
||||
func (mg *MailgunImpl) RemoveUnsubscribeWithTag(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(r)
|
||||
return err
|
||||
}
|
69
vendor/gopkg.in/mailgun/mailgun-go.v1/unsubscribes_test.go
generated
vendored
Normal file
69
vendor/gopkg.in/mailgun/mailgun-go.v1/unsubscribes_test.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func TestCreateUnsubscriber(t *testing.T) {
|
||||
email := randomEmail("unsubcribe", reqEnv(t, "MG_DOMAIN"))
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
// Create unsubscription record
|
||||
ensure.Nil(t, mg.Unsubscribe(email, "*"))
|
||||
}
|
||||
|
||||
func TestGetUnsubscribes(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
n, us, err := mg.GetUnsubscribes(DefaultLimit, DefaultSkip)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
t.Logf("Received %d out of %d unsubscribe records.\n", len(us), n)
|
||||
if len(us) > 0 {
|
||||
t.Log("ID\tAddress\tCreated At\tTags\t")
|
||||
for _, u := range us {
|
||||
t.Logf("%s\t%s\t%s\t%s\t\n", u.ID, u.Address, u.CreatedAt, u.Tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUnsubscriptionByAddress(t *testing.T) {
|
||||
email := randomEmail("unsubcribe", reqEnv(t, "MG_DOMAIN"))
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
// Create unsubscription record
|
||||
ensure.Nil(t, mg.Unsubscribe(email, "*"))
|
||||
|
||||
n, us, err := mg.GetUnsubscribesByAddress(email)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
t.Logf("Received %d out of %d unsubscribe records.\n", len(us), n)
|
||||
if len(us) > 0 {
|
||||
t.Log("ID\tAddress\tCreated At\tTags\t")
|
||||
for _, u := range us {
|
||||
t.Logf("%s\t%s\t%s\t%s\t\n", u.ID, u.Address, u.CreatedAt, u.Tags)
|
||||
}
|
||||
}
|
||||
// Destroy the unsubscription record
|
||||
ensure.Nil(t, mg.RemoveUnsubscribe(email))
|
||||
}
|
||||
|
||||
func TestCreateDestroyUnsubscription(t *testing.T) {
|
||||
email := randomEmail("unsubcribe", reqEnv(t, "MG_DOMAIN"))
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
// Create unsubscription record
|
||||
ensure.Nil(t, mg.Unsubscribe(email, "*"))
|
||||
|
||||
n, us, err := mg.GetUnsubscribesByAddress(email)
|
||||
ensure.Nil(t, err)
|
||||
t.Logf("Received %d out of %d unsubscribe records.\n", len(us), n)
|
||||
|
||||
// Destroy the unsubscription record
|
||||
ensure.Nil(t, mg.RemoveUnsubscribe(email))
|
||||
}
|
95
vendor/gopkg.in/mailgun/mailgun-go.v1/webhooks.go
generated
vendored
Normal file
95
vendor/gopkg.in/mailgun/mailgun-go.v1/webhooks.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GetWebhooks returns the complete set of webhooks configured for your domain.
|
||||
// Note that a zero-length mapping is not an error.
|
||||
func (mg *MailgunImpl) GetWebhooks() (map[string]string, error) {
|
||||
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
var envelope struct {
|
||||
Webhooks map[string]interface{} `json:"webhooks"`
|
||||
}
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
hooks := make(map[string]string, 0)
|
||||
if err != nil {
|
||||
return hooks, err
|
||||
}
|
||||
for k, v := range envelope.Webhooks {
|
||||
object := v.(map[string]interface{})
|
||||
url := object["url"]
|
||||
hooks[k] = url.(string)
|
||||
}
|
||||
return hooks, nil
|
||||
}
|
||||
|
||||
// CreateWebhook installs a new webhook for your domain.
|
||||
func (mg *MailgunImpl) CreateWebhook(t, u string) error {
|
||||
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
p := newUrlEncodedPayload()
|
||||
p.addValue("id", t)
|
||||
p.addValue("url", u)
|
||||
_, err := makePostRequest(r, p)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteWebhook removes the specified webhook from your domain's configuration.
|
||||
func (mg *MailgunImpl) DeleteWebhook(t string) error {
|
||||
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + t)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
_, err := makeDeleteRequest(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetWebhookByType retrieves the currently assigned webhook URL associated with the provided type of webhook.
|
||||
func (mg *MailgunImpl) GetWebhookByType(t string) (string, error) {
|
||||
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + t)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
var envelope struct {
|
||||
Webhook struct {
|
||||
Url string `json:"url"`
|
||||
} `json:"webhook"`
|
||||
}
|
||||
err := getResponseFromJSON(r, &envelope)
|
||||
return envelope.Webhook.Url, err
|
||||
}
|
||||
|
||||
// UpdateWebhook replaces one webhook setting for another.
|
||||
func (mg *MailgunImpl) UpdateWebhook(t, u string) error {
|
||||
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + t)
|
||||
r.setClient(mg.Client())
|
||||
r.setBasicAuth(basicAuthUser, mg.ApiKey())
|
||||
p := newUrlEncodedPayload()
|
||||
p.addValue("url", u)
|
||||
_, err := makePutRequest(r, p)
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
142
vendor/gopkg.in/mailgun/mailgun-go.v1/webhooks_test.go
generated
vendored
Normal file
142
vendor/gopkg.in/mailgun/mailgun-go.v1/webhooks_test.go
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
package mailgun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
)
|
||||
|
||||
func TestWebhookCRUD(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
var countHooks = func() int {
|
||||
hooks, err := mg.GetWebhooks()
|
||||
ensure.Nil(t, err)
|
||||
return len(hooks)
|
||||
}
|
||||
|
||||
hookCount := countHooks()
|
||||
|
||||
domainURL := randomDomainURL(10)
|
||||
ensure.Nil(t, mg.CreateWebhook("deliver", domainURL))
|
||||
defer func() {
|
||||
ensure.Nil(t, mg.DeleteWebhook("deliver"))
|
||||
newCount := countHooks()
|
||||
ensure.DeepEqual(t, newCount, hookCount)
|
||||
}()
|
||||
|
||||
newCount := countHooks()
|
||||
ensure.False(t, newCount <= hookCount)
|
||||
|
||||
theURL, err := mg.GetWebhookByType("deliver")
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, theURL, domainURL)
|
||||
|
||||
updatedDomainURL := randomDomainURL(10)
|
||||
ensure.Nil(t, mg.UpdateWebhook("deliver", updatedDomainURL))
|
||||
|
||||
hooks, err := mg.GetWebhooks()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
ensure.DeepEqual(t, hooks["deliver"], updatedDomainURL)
|
||||
}
|
||||
|
||||
var signedTests = []bool{
|
||||
true,
|
||||
false,
|
||||
}
|
||||
|
||||
func TestVerifyWebhookRequest_Form(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
for _, v := range signedTests {
|
||||
fields := getSignatureFields(mg.ApiKey(), v)
|
||||
req := buildFormRequest(fields)
|
||||
|
||||
verified, err := mg.VerifyWebhookRequest(req)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
if v != verified {
|
||||
t.Errorf("VerifyWebhookRequest should return '%v' but got '%v'", v, verified)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyWebhookRequest_MultipartForm(t *testing.T) {
|
||||
mg, err := NewMailgunFromEnv()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
for _, v := range signedTests {
|
||||
fields := getSignatureFields(mg.ApiKey(), v)
|
||||
req := buildMultipartFormRequest(fields)
|
||||
|
||||
verified, err := mg.VerifyWebhookRequest(req)
|
||||
ensure.Nil(t, err)
|
||||
|
||||
if v != verified {
|
||||
t.Errorf("VerifyWebhookRequest should return '%v' but got '%v'", v, verified)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildFormRequest(fields map[string]string) *http.Request {
|
||||
values := url.Values{}
|
||||
|
||||
for k, v := range fields {
|
||||
values.Add(k, v)
|
||||
}
|
||||
|
||||
r := strings.NewReader(values.Encode())
|
||||
req, _ := http.NewRequest("POST", "/", r)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func buildMultipartFormRequest(fields map[string]string) *http.Request {
|
||||
buf := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(buf)
|
||||
|
||||
for k, v := range fields {
|
||||
writer.WriteField(k, v)
|
||||
}
|
||||
|
||||
writer.Close()
|
||||
|
||||
req, _ := http.NewRequest("POST", "/", buf)
|
||||
req.Header.Set("Content-type", writer.FormDataContentType())
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func getSignatureFields(key string, signed bool) map[string]string {
|
||||
badSignature := hex.EncodeToString([]byte("badsignature"))
|
||||
|
||||
fields := map[string]string{
|
||||
"token": "token",
|
||||
"timestamp": "123456789",
|
||||
"signature": badSignature,
|
||||
}
|
||||
|
||||
if signed {
|
||||
h := hmac.New(sha256.New, []byte(key))
|
||||
io.WriteString(h, fields["timestamp"])
|
||||
io.WriteString(h, fields["token"])
|
||||
hash := h.Sum(nil)
|
||||
|
||||
fields["signature"] = hex.EncodeToString(hash)
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
Reference in New Issue
Block a user