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