/mailgun.go

https://github.com/travelton/mailgun-go · Go · 290 lines · 148 code · 19 blank · 123 comment · 6 complexity · 237686b39d76a6ad0a873e555d5e138c MD5 · raw file

  1. // TODO(sfalvo):
  2. // Document how to run acceptance tests.
  3. // The mailgun package provides methods for interacting with the Mailgun API.
  4. // It automates the HTTP request/response cycle, encodings, and other details needed by the API.
  5. // This SDK lets you do everything the API lets you, in a more Go-friendly way.
  6. //
  7. // For further information please see the Mailgun documentation at
  8. // http://documentation.mailgun.com/
  9. //
  10. // Original Author: Michael Banzon
  11. // Contributions: Samuel A. Falvo II <sam.falvo %at% rackspace.com>
  12. // Version: 0.99.0
  13. //
  14. // Examples
  15. //
  16. // This document includes a number of examples which illustrates some aspects of the GUI which might be misleading or confusing.
  17. // All examples included are derived from an acceptance test.
  18. // Note that every SDK function has a corresponding acceptance test, so
  19. // if you don't find an example for a function you'd like to know more about,
  20. // please check the acceptance sub-package for a corresponding test.
  21. // Of course, contributions to the documentation are always welcome as well.
  22. // Feel free to submit a pull request or open a Github issue if you cannot find an example to suit your needs.
  23. //
  24. // Limit and Skip Settings
  25. //
  26. // Many SDK functions consume a pair of parameters called limit and skip.
  27. // These help control how much data Mailgun sends over the wire.
  28. // Limit, as you'd expect, gives a count of the number of records you want to receive.
  29. // Note that, at present, Mailgun imposes its own cap of 100, for all API endpoints.
  30. // Skip indicates where in the data set you want to start receiving from.
  31. // Mailgun defaults to the very beginning of the dataset if not specified explicitly.
  32. //
  33. // If you don't particularly care how much data you receive, you may specify DefaultLimit.
  34. // If you similarly don't care about where the data starts, you may specify DefaultSkip.
  35. //
  36. // Functions that Return Totals
  37. //
  38. // Functions which accept a limit and skip setting, in general,
  39. // will also return a total count of the items returned.
  40. // Note that this total count is not the total in the bundle returned by the call.
  41. // You can determine that easily enough with Go's len() function.
  42. // The total that you receive actually refers to the complete set of data on the server.
  43. // This total may well exceed the size returned from the API.
  44. //
  45. // If this happens, you may find yourself needing to iterate over the dataset of interest.
  46. // For example:
  47. //
  48. // // Get total amount of stuff we have to work with.
  49. // mg := NewMailgun("example.com", "my_api_key", "")
  50. // n, _, err := mg.GetStats(1, 0, nil, "sent", "opened")
  51. // if err != nil {
  52. // t.Fatal(err)
  53. // }
  54. // // Loop over it all.
  55. // for sk := 0; sk < n; sk += limit {
  56. // _, stats, err := mg.GetStats(limit, sk, nil, "sent", "opened")
  57. // if err != nil {
  58. // t.Fatal(err)
  59. // }
  60. // doSomethingWith(stats)
  61. // }
  62. //
  63. // License
  64. //
  65. // Copyright (c) 2013-2014, Michael Banzon.
  66. // All rights reserved.
  67. //
  68. // Redistribution and use in source and binary forms, with or without modification,
  69. // are permitted provided that the following conditions are met:
  70. //
  71. // * Redistributions of source code must retain the above copyright notice, this
  72. // list of conditions and the following disclaimer.
  73. //
  74. // * Redistributions in binary form must reproduce the above copyright notice, this
  75. // list of conditions and the following disclaimer in the documentation and/or
  76. // other materials provided with the distribution.
  77. //
  78. // * Neither the names of Mailgun, Michael Banzon, nor the names of their
  79. // contributors may be used to endorse or promote products derived from
  80. // this software without specific prior written permission.
  81. //
  82. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  83. // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  84. // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  85. // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  86. // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  87. // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  88. // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  89. // ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  90. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  91. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  92. package mailgun
  93. import (
  94. "fmt"
  95. "github.com/mbanzon/simplehttp"
  96. "io"
  97. "time"
  98. )
  99. const (
  100. apiBase = "https://api.mailgun.net/v2"
  101. messagesEndpoint = "messages"
  102. mimeMessagesEndpoint = "messages.mime"
  103. addressValidateEndpoint = "address/validate"
  104. addressParseEndpoint = "address/parse"
  105. bouncesEndpoint = "bounces"
  106. statsEndpoint = "stats"
  107. domainsEndpoint = "domains"
  108. deleteTagEndpoint = "tags"
  109. campaignsEndpoint = "campaigns"
  110. eventsEndpoint = "events"
  111. credentialsEndpoint = "credentials"
  112. unsubscribesEndpoint = "unsubscribes"
  113. routesEndpoint = "routes"
  114. webhooksEndpoint = "webhooks"
  115. listsEndpoint = "lists"
  116. basicAuthUser = "api"
  117. )
  118. // Mailgun defines the supported subset of the Mailgun API.
  119. // The Mailgun API may contain additional features which have been deprecated since writing this SDK.
  120. // This SDK only covers currently supported interface endpoints.
  121. //
  122. // Note that Mailgun reserves the right to deprecate endpoints.
  123. // Some endpoints listed in this interface may, at any time, become obsolete.
  124. // Always double-check with the Mailgun API Documentation to
  125. // determine the currently supported feature set.
  126. type Mailgun interface {
  127. Domain() string
  128. ApiKey() string
  129. PublicApiKey() string
  130. Send(m *Message) (string, string, error)
  131. ValidateEmail(email string) (EmailVerification, error)
  132. ParseAddresses(addresses ...string) ([]string, []string, error)
  133. GetBounces(limit, skip int) (int, []Bounce, error)
  134. GetSingleBounce(address string) (Bounce, error)
  135. AddBounce(address, code, error string) error
  136. DeleteBounce(address string) error
  137. GetStats(limit int, skip int, startDate *time.Time, event ...string) (int, []Stat, error)
  138. DeleteTag(tag string) error
  139. GetDomains(limit, skip int) (int, []Domain, error)
  140. GetSingleDomain(domain string) (Domain, []DNSRecord, []DNSRecord, error)
  141. CreateDomain(name string, smtpPassword string, spamAction string, wildcard bool) error
  142. DeleteDomain(name string) error
  143. GetCampaigns() (int, []Campaign, error)
  144. CreateCampaign(name, id string) error
  145. UpdateCampaign(oldId, name, newId string) error
  146. DeleteCampaign(id string) error
  147. GetComplaints(limit, skip int) (int, []Complaint, error)
  148. GetSingleComplaint(address string) (Complaint, error)
  149. GetStoredMessage(id string) (StoredMessage, error)
  150. DeleteStoredMessage(id string) error
  151. GetCredentials(limit, skip int) (int, []Credential, error)
  152. CreateCredential(login, password string) error
  153. ChangeCredentialPassword(id, password string) error
  154. DeleteCredential(id string) error
  155. GetUnsubscribes(limit, skip int) (int, []Unsubscription, error)
  156. GetUnsubscribesByAddress(string) (int, []Unsubscription, error)
  157. Unsubscribe(address, tag string) error
  158. RemoveUnsubscribe(string) error
  159. CreateComplaint(string) error
  160. DeleteComplaint(string) error
  161. GetRoutes(limit, skip int) (int, []Route, error)
  162. GetRouteByID(string) (Route, error)
  163. CreateRoute(Route) (Route, error)
  164. DeleteRoute(string) error
  165. UpdateRoute(string, Route) (Route, error)
  166. GetWebhooks() (map[string]string, error)
  167. CreateWebhook(kind, url string) error
  168. DeleteWebhook(kind string) error
  169. GetWebhookByType(kind string) (string, error)
  170. UpdateWebhook(kind, url string) error
  171. GetLists(limit, skip int, filter string) (int, []List, error)
  172. CreateList(List) (List, error)
  173. DeleteList(string) error
  174. GetListByAddress(string) (List, error)
  175. UpdateList(string, List) (List, error)
  176. GetMembers(limit, skip int, subfilter *bool, address string) (int, []Member, error)
  177. GetMemberByAddress(MemberAddr, listAddr string) (Member, error)
  178. CreateMember(merge bool, addr string, prototype Member) error
  179. CreateMemberList(subscribed *bool, addr string, newMembers []interface{}) error
  180. UpdateMember(Member, list string, prototype Member) (Member, error)
  181. DeleteMember(Member, list string) error
  182. NewMessage(from, subject, text string, to ...string) *Message
  183. NewMIMEMessage(body io.ReadCloser, to ...string) *Message
  184. NewEventIterator() *EventIterator
  185. }
  186. // MailgunImpl bundles data needed by a large number of methods in order to interact with the Mailgun API.
  187. // Colloquially, we refer to instances of this structure as "clients."
  188. type MailgunImpl struct {
  189. domain string
  190. apiKey string
  191. publicApiKey string
  192. }
  193. // NewMailGun creates a new client instance.
  194. func NewMailgun(domain, apiKey, publicApiKey string) Mailgun {
  195. m := MailgunImpl{domain: domain, apiKey: apiKey, publicApiKey: publicApiKey}
  196. return &m
  197. }
  198. // Domain returns the domain configured for this client.
  199. func (m *MailgunImpl) Domain() string {
  200. return m.domain
  201. }
  202. // ApiKey returns the API key configured for this client.
  203. func (m *MailgunImpl) ApiKey() string {
  204. return m.apiKey
  205. }
  206. // PublicApiKey returns the public API key configured for this client.
  207. func (m *MailgunImpl) PublicApiKey() string {
  208. return m.publicApiKey
  209. }
  210. // generateApiUrl renders a URL for an API endpoint using the domain and endpoint name.
  211. func generateApiUrl(m Mailgun, endpoint string) string {
  212. return fmt.Sprintf("%s/%s/%s", apiBase, m.Domain(), endpoint)
  213. }
  214. // generateMemberApiUrl renders a URL relevant for specifying mailing list members.
  215. // The address parameter refers to the mailing list in question.
  216. func generateMemberApiUrl(endpoint, address string) string {
  217. return fmt.Sprintf("%s/%s/%s/members", apiBase, endpoint, address)
  218. }
  219. // generateApiUrlWithTarget works as generateApiUrl,
  220. // but consumes an additional resource parameter called 'target'.
  221. func generateApiUrlWithTarget(m Mailgun, endpoint, target string) string {
  222. tail := ""
  223. if target != "" {
  224. tail = fmt.Sprintf("/%s", target)
  225. }
  226. return fmt.Sprintf("%s%s", generateApiUrl(m, endpoint), tail)
  227. }
  228. // generateDomainApiUrl renders a URL as generateApiUrl, but
  229. // addresses a family of functions which have a non-standard URL structure.
  230. // Most URLs consume a domain in the 2nd position, but some endpoints
  231. // require the word "domains" to be there instead.
  232. func generateDomainApiUrl(m Mailgun, endpoint string) string {
  233. return fmt.Sprintf("%s/domains/%s/%s", apiBase, m.Domain(), endpoint)
  234. }
  235. // generateCredentialsUrl renders a URL as generateDomainApiUrl,
  236. // but focuses on the SMTP credentials family of API functions.
  237. func generateCredentialsUrl(m Mailgun, id string) string {
  238. tail := ""
  239. if id != "" {
  240. tail = fmt.Sprintf("/%s", id)
  241. }
  242. return generateDomainApiUrl(m, fmt.Sprintf("credentials%s", tail))
  243. // return fmt.Sprintf("%s/domains/%s/credentials%s", apiBase, m.Domain(), tail)
  244. }
  245. // generateStoredMessageUrl generates the URL needed to acquire a copy of a stored message.
  246. func generateStoredMessageUrl(m Mailgun, endpoint, id string) string {
  247. return generateDomainApiUrl(m, fmt.Sprintf("%s/%s", endpoint, id))
  248. // return fmt.Sprintf("%s/domains/%s/%s/%s", apiBase, m.Domain(), endpoint, id)
  249. }
  250. // generatePublicApiUrl works as generateApiUrl, except that generatePublicApiUrl has no need for the domain.
  251. func generatePublicApiUrl(endpoint string) string {
  252. return fmt.Sprintf("%s/%s", apiBase, endpoint)
  253. }
  254. // generateParameterizedUrl works as generateApiUrl, but supports query parameters.
  255. func generateParameterizedUrl(m Mailgun, endpoint string, payload simplehttp.Payload) (string, error) {
  256. paramBuffer, err := payload.GetPayloadBuffer()
  257. if err != nil {
  258. return "", err
  259. }
  260. params := string(paramBuffer.Bytes())
  261. return fmt.Sprintf("%s?%s", generateApiUrl(m, eventsEndpoint), params), nil
  262. }
  263. // parseMailgunTime translates a timestamp as returned by Mailgun into a Go standard timestamp.
  264. func parseMailgunTime(ts string) (t time.Time, err error) {
  265. t, err = time.Parse("Mon, 2 Jan 2006 15:04:05 MST", ts)
  266. return
  267. }
  268. // formatMailgunTime translates a timestamp into a human-readable form.
  269. func formatMailgunTime(t *time.Time) string {
  270. return t.Format("Mon, 2 Jan 2006 15:04:05 MST")
  271. }