From 2f0fd351a8067da7a4e31d219b1d7f5c9a417baa Mon Sep 17 00:00:00 2001
From: Matthew Dillon <mrdillon@alaska.edu>
Date: Mon, 29 Jun 2015 06:16:48 -0800
Subject: [PATCH] Rough in mailgun support

---
 Godeps/Godeps.json                            |   8 +
 .../github.com/mailgun/mailgun-go/.travis.yml |  10 +
 .../src/github.com/mailgun/mailgun-go/LICENSE |  27 +
 .../github.com/mailgun/mailgun-go/README.md   |  20 +
 .../mailgun-go/acceptance/acceptance.go       |  29 +
 .../mailgun-go/acceptance/bounces_test.go     | 119 ++++
 .../mailgun-go/acceptance/credentials_test.go |  53 ++
 .../mailgun-go/acceptance/domains_test.go     |  96 +++
 .../acceptance/email_validation_test.go       |  52 ++
 .../mailgun-go/acceptance/events_test.go      |  41 ++
 .../acceptance/mailing_lists_test.go          | 205 ++++++
 .../mailgun-go/acceptance/messages_test.go    | 329 +++++++++
 .../mailgun-go/acceptance/routes_test.go      |  87 +++
 .../acceptance/spam_complaints_test.go        |  71 ++
 .../mailgun-go/acceptance/stats_test.go       |  40 ++
 .../acceptance/unsubscribes_test.go           |  71 ++
 .../mailgun-go/acceptance/webhooks_test.go    |  66 ++
 .../github.com/mailgun/mailgun-go/bounces.go  | 122 ++++
 .../mailgun/mailgun-go/campaigns.go           |  79 +++
 .../mailgun/mailgun-go/credentials.go         |  76 ++
 .../github.com/mailgun/mailgun-go/domains.go  | 121 ++++
 .../mailgun/mailgun-go/email_validation.go    |  71 ++
 .../github.com/mailgun/mailgun-go/events.go   | 130 ++++
 .../mailgun/mailgun-go/examples_test.go       | 117 ++++
 .../github.com/mailgun/mailgun-go/mailgun.go  | 291 ++++++++
 .../mailgun/mailgun-go/mailgun_test.go        |  68 ++
 .../mailgun/mailgun-go/mailing_lists.go       | 307 +++++++++
 .../github.com/mailgun/mailgun-go/messages.go | 647 ++++++++++++++++++
 .../mailgun/mailgun-go/rest_shim.go           | 152 ++++
 .../github.com/mailgun/mailgun-go/routes.go   | 127 ++++
 .../mailgun/mailgun-go/spam_complaints.go     |  78 +++
 .../github.com/mailgun/mailgun-go/stats.go    |  59 ++
 .../mailgun/mailgun-go/unsubscribes.go        |  66 ++
 .../github.com/mailgun/mailgun-go/webhooks.go |  68 ++
 .../github.com/mbanzon/simplehttp/.gitignore  |   1 +
 .../github.com/mbanzon/simplehttp/.travis.yml |  11 +
 .../src/github.com/mbanzon/simplehttp/LICENSE |  27 +
 .../github.com/mbanzon/simplehttp/README.md   |  15 +
 .../github.com/mbanzon/simplehttp/helpers.go  |  17 +
 .../mbanzon/simplehttp/helpers_test.go        | 133 ++++
 .../mbanzon/simplehttp/json_utils.go          |  31 +
 .../github.com/mbanzon/simplehttp/parsing.go  |  15 +
 .../mbanzon/simplehttp/payload_test.go        |  38 +
 .../github.com/mbanzon/simplehttp/payloads.go | 141 ++++
 .../mbanzon/simplehttp/shorthand.go           |  74 ++
 .../mbanzon/simplehttp/shorthand_test.go      | 104 +++
 .../mbanzon/simplehttp/simplehttp.go          | 147 ++++
 .../mbanzon/simplehttp/simplehttp_test.go     |  37 +
 helpers.go                                    |   5 +-
 main.go                                       |  19 +
 users.go                                      |  36 +-
 51 files changed, 4749 insertions(+), 5 deletions(-)
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/.travis.yml
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/LICENSE
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/README.md
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/acceptance.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/bounces_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/credentials_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/domains_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/email_validation_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/events_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/mailing_lists_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/messages_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/routes_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/spam_complaints_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/stats_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/unsubscribes_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/webhooks_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/bounces.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/campaigns.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/credentials.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/domains.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/email_validation.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/events.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/examples_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailgun.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailgun_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailing_lists.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/messages.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/rest_shim.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/routes.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/spam_complaints.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/stats.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/unsubscribes.go
 create mode 100644 Godeps/_workspace/src/github.com/mailgun/mailgun-go/webhooks.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/.gitignore
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/.travis.yml
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/LICENSE
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/README.md
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/helpers.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/helpers_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/json_utils.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/parsing.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/payload_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/payloads.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/shorthand.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/shorthand_test.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/simplehttp.go
 create mode 100644 Godeps/_workspace/src/github.com/mbanzon/simplehttp/simplehttp_test.go

diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 7eb8f29..e9fd01b 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -37,6 +37,14 @@
 			"Comment": "go1.0-cutoff-29-g30ed220",
 			"Rev": "30ed2200d7ec99cf17272292f1d4b7b0bd7165db"
 		},
+		{
+			"ImportPath": "github.com/mailgun/mailgun-go",
+			"Rev": "9578dc67692294bb7e2a6f4b15dd18c97af19440"
+		},
+		{
+			"ImportPath": "github.com/mbanzon/simplehttp",
+			"Rev": "04c542e7ac706a25820090f274ea6a4f39a63326"
+		},
 		{
 			"ImportPath": "github.com/thermokarst/jwt",
 			"Rev": "66ca404d841ed908aa6cc9361107b0c363c94cd8"
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/.travis.yml b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/.travis.yml
new file mode 100644
index 0000000..baf3341
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/.travis.yml
@@ -0,0 +1,10 @@
+language: go
+go:
+  - 1.1.2
+  - 1.2
+  - tip
+env:
+  - GOARCH=amd64
+  - GOARCH=386
+script:
+  - go test
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/LICENSE b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/LICENSE
new file mode 100644
index 0000000..24b24c4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/LICENSE
@@ -0,0 +1,27 @@
+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 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.
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/README.md b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/README.md
new file mode 100644
index 0000000..0931fbc
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/README.md
@@ -0,0 +1,20 @@
+Mailgun with Go
+===============
+
+[![Build Status](https://travis-ci.org/mbanzon/mailgun.png?branch=master)](https://travis-ci.org/mbanzon/mailgun)
+[![GoDoc](https://godoc.org/github.com/mailgun/mailgun-go?status.svg)](https://godoc.org/github.com/mailgun/mailgun-go)
+
+Go library for sending mail with the Mailgun API.
+
+
+See these examples on how how to use use the library with various parts of the Mailgun API:
+
+* [Messages](https://gist.github.com/mbanzon/8179682 "mailgun-message-example.go")
+* [E-mail validation](https://gist.github.com/mbanzon/8179989 "mailgun-validation-example.go")
+* [Bounces](https://gist.github.com/mbanzon/8179951 "mailgun-bounces-example.go")
+* [Stats](https://gist.github.com/mbanzon/8206266 "mailgun-stats-example.go")
+* [File Attachment from Memory](https://gist.github.com/sym3tri/8a29ddecd65ec4f8ccfc)
+
+More examples are coming soon.
+
+The code is released under a 3-clause BSD license. See the LICENSE file for more information.
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/acceptance.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/acceptance.go
new file mode 100644
index 0000000..13c5506
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/acceptance.go
@@ -0,0 +1,29 @@
+// +build acceptance
+
+// The acceptance test package includes utilities supporting acceptance tests in *_test.go
+// files.  To execute these acceptance tests, you must invoke them using the acceptance
+// build tag, like so:
+//
+// $ go test -tags acceptance github.com/mailgun/mailgun-go
+//
+// Note that some API calls may potentially cost the user money!  By default, such tests
+// do NOT run.  However, you will then not be testing the full capability of Mailgun.
+// To run them, you'll also need to specify the spendMoney build tag:
+//
+// $ go test -tags "acceptance spendMoney" github.com/mailgun/mailgun-go
+package acceptance
+
+import (
+	"os"
+	"testing"
+)
+
+// 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 *testing.T, variableName string) string {
+	value := os.Getenv(variableName)
+	if value == "" {
+		t.Fatalf("Expected environment variable %s to be set", variableName)
+	}
+	return value
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/bounces_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/bounces_test.go
new file mode 100644
index 0000000..15f323a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/bounces_test.go
@@ -0,0 +1,119 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"fmt"
+	mailgun "github.com/mailgun/mailgun-go"
+	"testing"
+)
+
+func TestGetBounces(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	n, bounces, err := mg.GetBounces(-1, -1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if n > 0 {
+		t.Fatal("Expected no bounces for what should be a clean domain.")
+	}
+	if n != len(bounces) {
+		t.Fatalf("Expected length of bounces %d to equal returned length %d", len(bounces), n)
+	}
+}
+
+func TestGetSingleBounce(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	exampleEmail := fmt.Sprintf("baz@%s", domain)
+	_, err := mg.GetSingleBounce(exampleEmail)
+	if err == nil {
+		t.Fatal("Did not expect a bounce to exist")
+	}
+	ure, ok := err.(*mailgun.UnexpectedResponseError)
+	if !ok {
+		t.Fatal("Expected UnexpectedResponseError")
+	}
+	if ure.Actual != 404 {
+		t.Fatalf("Expected 404 response code; got %d", ure.Actual)
+	}
+}
+
+func TestAddDelBounces(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+
+	// Compute an e-mail address for our domain.
+
+	exampleEmail := fmt.Sprintf("baz@%s", domain)
+
+	// First, basic sanity check.
+	// Fail early if we have bounces for a fictitious e-mail address.
+
+	n, _, err := mg.GetBounces(-1, -1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if n > 0 {
+		t.Fatal("Expected no bounces for what should be a clean domain.")
+	}
+
+	bounce, err := mg.GetSingleBounce(exampleEmail)
+	if err == nil {
+		t.Fatalf("Expected no bounces for %s", exampleEmail)
+	}
+
+	// Add the bounce for our address.
+
+	err = mg.AddBounce(exampleEmail, "550", "TestAddDelBounces-generated error")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// We should now have one bounce listed when we query the API.
+
+	n, bounces, err := mg.GetBounces(-1, -1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if n != 1 {
+		t.Fatal("Expected one bounce for this domain.")
+	}
+	if bounces[0].Address != exampleEmail {
+		t.Fatalf("Expected bounce for address %s; got %s", exampleEmail, bounces[0].Address)
+	}
+
+	bounce, err = mg.GetSingleBounce(exampleEmail)
+	if err != nil {
+		t.Fatal(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)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Make sure we're back to the way we were.
+
+	n, _, err = mg.GetBounces(-1, -1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if n > 0 {
+		t.Fatal("Expected no bounces for what should be a clean domain.")
+	}
+
+	_, err = mg.GetSingleBounce(exampleEmail)
+	if err == nil {
+		t.Fatalf("Expected no bounces for %s", exampleEmail)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/credentials_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/credentials_test.go
new file mode 100644
index 0000000..5b90484
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/credentials_test.go
@@ -0,0 +1,53 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"fmt"
+	mailgun "github.com/mailgun/mailgun-go"
+	"os"
+	"testing"
+	"text/tabwriter"
+)
+
+func TestGetCredentials(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	n, cs, err := mg.GetCredentials(mailgun.DefaultLimit, mailgun.DefaultSkip)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tw := &tabwriter.Writer{}
+	tw.Init(os.Stdout, 2, 8, 2, ' ', 0)
+	fmt.Fprintf(tw, "Login\tCreated At\t\n")
+	for _, c := range cs {
+		fmt.Fprintf(tw, "%s\t%s\t\n", c.Login, c.CreatedAt)
+	}
+	tw.Flush()
+	fmt.Printf("%d credentials listed out of %d\n", len(cs), n)
+}
+
+func TestCreateDeleteCredentials(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	randomPassword := randomString(16, "pw")
+	randomID := randomString(16, "usr")
+	randomLogin := fmt.Sprintf("%s@%s", randomID, domain)
+
+	err := mg.CreateCredential(randomLogin, randomPassword)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = mg.ChangeCredentialPassword(randomID, randomString(16, "pw2"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = mg.DeleteCredential(randomID)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/domains_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/domains_test.go
new file mode 100644
index 0000000..a6415c3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/domains_test.go
@@ -0,0 +1,96 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"crypto/rand"
+	"fmt"
+	"github.com/mailgun/mailgun-go"
+	"testing"
+)
+
+func TestGetDomains(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	n, domains, err := mg.GetDomains(mailgun.DefaultLimit, mailgun.DefaultSkip)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Printf("TestGetDomains: %d domains retrieved\n", n)
+	for _, d := range domains {
+		fmt.Printf("TestGetDomains: %#v\n", d)
+	}
+}
+
+func TestGetSingleDomain(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	_, domains, err := mg.GetDomains(mailgun.DefaultLimit, mailgun.DefaultSkip)
+	if err != nil {
+		t.Fatal(err)
+	}
+	dr, rxDnsRecords, txDnsRecords, err := mg.GetSingleDomain(domains[0].Name)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fmt.Printf("TestGetSingleDomain: %#v\n", dr)
+	for _, rxd := range rxDnsRecords {
+		fmt.Printf("TestGetSingleDomains:   %#v\n", rxd)
+	}
+	for _, txd := range txDnsRecords {
+		fmt.Printf("TestGetSingleDomains:   %#v\n", txd)
+	}
+}
+
+func TestGetSingleDomainNotExist(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	_, _, _, err := mg.GetSingleDomain(randomString(32, "com.edu.org.")+".com")
+	if err == nil {
+		t.Fatal("Did not expect a domain to exist")
+	}
+	ure, ok := err.(*mailgun.UnexpectedResponseError)
+	if !ok {
+		t.Fatal("Expected UnexpectedResponseError")
+	}
+	if ure.Actual != 404 {
+		t.Fatalf("Expected 404 response code; got %d", ure.Actual)
+	}
+}
+
+func TestAddDeleteDomain(t *testing.T) {
+	// First, we need to add the domain.
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	randomDomainName := randomString(16, "DOMAIN") + ".example.com"
+	randomPassword := randomString(16, "PASSWD")
+	err := mg.CreateDomain(randomDomainName, randomPassword, mailgun.Tag, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Next, we delete it.
+	err = mg.DeleteDomain(randomDomainName)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+// 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)
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/email_validation_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/email_validation_test.go
new file mode 100644
index 0000000..76fc277
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/email_validation_test.go
@@ -0,0 +1,52 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"github.com/mailgun/mailgun-go"
+	"testing"
+)
+
+func TestEmailValidation(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, "", apiKey)
+	ev, err := mg.ValidateEmail("foo@mailgun.com")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if ev.IsValid != true {
+		t.Fatal("Expected a valid e-mail address")
+	}
+	if ev.Parts.DisplayName != "" {
+		t.Fatal("No display name should exist")
+	}
+	if ev.Parts.LocalPart != "foo" {
+		t.Fatal("Expected local part of foo; got ", ev.Parts.LocalPart)
+	}
+	if ev.Parts.Domain != "mailgun.com" {
+		t.Fatal("Expected mailgun.com domain; got ", ev.Parts.Domain)
+	}
+}
+
+func TestParseAddresses(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, "", apiKey)
+	addressesThatParsed, unparsableAddresses, err := mg.ParseAddresses("Alice <alice@example.com>", "bob@example.com", "example.com")
+	if err != nil {
+		t.Fatal(err)
+	}
+	hittest := map[string]bool{
+		"Alice <alice@example.com>": true,
+		"bob@example.com":           true,
+	}
+	for _, a := range addressesThatParsed {
+		if !hittest[a] {
+			t.Fatalf("Expected %s to be parsable", a)
+		}
+	}
+	if len(unparsableAddresses) != 1 {
+		t.Fatalf("Expected 1 address to be unparsable; got %d", len(unparsableAddresses))
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/events_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/events_test.go
new file mode 100644
index 0000000..7f756af
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/events_test.go
@@ -0,0 +1,41 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"fmt"
+	"github.com/mailgun/mailgun-go"
+	"os"
+	"testing"
+	"text/tabwriter"
+)
+
+func TestEventIterator(t *testing.T) {
+	// Grab the list of events (as many as we can get)
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	ei := mg.NewEventIterator()
+	err := ei.GetFirstPage(mailgun.GetEventsOptions{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Print out the kind of event and timestamp.
+	// Specifics about each event will depend on the "event" type.
+	events := ei.Events()
+	tw := &tabwriter.Writer{}
+	tw.Init(os.Stdout, 2, 8, 2, ' ', tabwriter.AlignRight)
+	fmt.Fprintln(tw, "Event\tTimestamp\t")
+	for _, event := range events {
+		fmt.Fprintf(tw, "%s\t%v\t\n", event["event"], event["timestamp"])
+	}
+	tw.Flush()
+	fmt.Printf("%d events dumped\n\n", len(events))
+
+	// We're on the first page.  We must at the beginning.
+	ei.GetPrevious()
+	if len(ei.Events()) != 0 {
+		t.Fatal("Expected to be at the beginning")
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/mailing_lists_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/mailing_lists_test.go
new file mode 100644
index 0000000..69286bb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/mailing_lists_test.go
@@ -0,0 +1,205 @@
+// +build acceptance,spendMoney
+
+package acceptance
+
+import (
+	"fmt"
+	mailgun "github.com/mailgun/mailgun-go"
+	"testing"
+)
+
+func setup(t *testing.T) (mailgun.Mailgun, string) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+
+	address := fmt.Sprintf("list5@%s", domain)
+	_, err := mg.CreateList(mailgun.List{
+		Address:     address,
+		Name:        address,
+		Description: "TestMailingListMembers-related mailing list",
+		AccessLevel: mailgun.Members,
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	return mg, address
+}
+
+func teardown(t *testing.T, mg mailgun.Mailgun, address string) {
+	err := mg.DeleteList(address)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestMailingListMembers(t *testing.T) {
+	mg, address := setup(t)
+	defer teardown(t, mg, address)
+
+	var countPeople = func() int {
+		n, _, err := mg.GetMembers(mailgun.DefaultLimit, mailgun.DefaultSkip, mailgun.All, address)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return n
+	}
+
+	startCount := countPeople()
+	protoJoe := mailgun.Member{
+		Address:    "joe@example.com",
+		Name:       "Joe Example",
+		Subscribed: mailgun.Subscribed,
+	}
+	err := mg.CreateMember(true, address, protoJoe)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	newCount := countPeople()
+	if newCount <= startCount {
+		t.Fatalf("Expected %d people subscribed; got %d", startCount+1, newCount)
+	}
+
+	theMember, err := mg.GetMemberByAddress("joe@example.com", address)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if (theMember.Address != protoJoe.Address) ||
+		(theMember.Name != protoJoe.Name) ||
+		(*theMember.Subscribed != *protoJoe.Subscribed) ||
+		(len(theMember.Vars) != 0) {
+		t.Fatalf("Unexpected Member: Expected [%#v], Got [%#v]", protoJoe, theMember)
+	}
+
+	_, err = mg.UpdateMember("joe@example.com", address, mailgun.Member{
+		Name: "Joe Cool",
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	theMember, err = mg.GetMemberByAddress("joe@example.com", address)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if theMember.Name != "Joe Cool" {
+		t.Fatal("Expected Joe Cool; got " + theMember.Name)
+	}
+
+	err = mg.DeleteMember("joe@example.com", address)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if countPeople() != startCount {
+		t.Fatalf("Expected %d people; got %d instead", startCount, countPeople())
+	}
+
+	err = mg.CreateMemberList(nil, address, []interface{}{
+		mailgun.Member{
+			Address:    "joe.user1@example.com",
+			Name:       "Joe's debugging account",
+			Subscribed: mailgun.Unsubscribed,
+		},
+		mailgun.Member{
+			Address:    "Joe Cool <joe.user2@example.com>",
+			Name:       "Joe's Cool Account",
+			Subscribed: mailgun.Subscribed,
+		},
+		mailgun.Member{
+			Address: "joe.user3@example.com",
+			Vars: map[string]interface{}{
+				"packet-email": "KW9ABC @ BOGBBS-4.#NCA.CA.USA.NOAM",
+			},
+		},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	theMember, err = mg.GetMemberByAddress("joe.user2@example.com", address)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if theMember.Name != "Joe's Cool Account" {
+		t.Fatalf("Expected Joe's Cool Account; got %s", theMember.Name)
+	}
+	if theMember.Subscribed != nil {
+		if *theMember.Subscribed != true {
+			t.Fatalf("Expected subscribed to be true; got %v", *theMember.Subscribed)
+		}
+	} else {
+		t.Fatal("Expected some kind of subscription status; got nil.")
+	}
+}
+
+func TestMailingLists(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	listAddr := fmt.Sprintf("list2@%s", domain)
+	protoList := mailgun.List{
+		Address:     listAddr,
+		Name:        "List1",
+		Description: "A list created by an acceptance test.",
+		AccessLevel: mailgun.Members,
+	}
+
+	var countLists = func() int {
+		total, _, err := mg.GetLists(mailgun.DefaultLimit, mailgun.DefaultSkip, "")
+		if err != nil {
+			t.Fatal(err)
+		}
+		return total
+	}
+
+	startCount := countLists()
+
+	_, err := mg.CreateList(protoList)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		err = mg.DeleteList(listAddr)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		newCount := countLists()
+		if newCount != startCount {
+			t.Fatalf("Expected %d lists defined; got %d", startCount, newCount)
+		}
+	}()
+
+	newCount := countLists()
+	if newCount <= startCount {
+		t.Fatalf("Expected %d lists defined; got %d", startCount+1, newCount)
+	}
+
+	theList, err := mg.GetListByAddress(listAddr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	protoList.CreatedAt = theList.CreatedAt // ignore this field when comparing.
+	if theList != protoList {
+		t.Fatalf("Unexpected list descriptor: Expected [%#v], Got [%#v]", protoList, theList)
+	}
+
+	_, err = mg.UpdateList(listAddr, mailgun.List{
+		Description: "A list whose description changed",
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	theList, err = mg.GetListByAddress(listAddr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	newList := protoList
+	newList.Description = "A list whose description changed"
+	if theList != newList {
+		t.Fatalf("Expected [%#v], Got [%#v]", newList, theList)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/messages_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/messages_test.go
new file mode 100644
index 0000000..1c27301
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/messages_test.go
@@ -0,0 +1,329 @@
+// +build acceptance,spendMoney
+
+package acceptance
+
+import (
+	"fmt"
+	mailgun "github.com/mailgun/mailgun-go"
+	"io/ioutil"
+	"strings"
+	"testing"
+	"time"
+)
+
+const (
+	fromUser       = "=?utf-8?q?Katie_Brewer=2C_CFP=C2=AE?= <joe@example.com>"
+	exampleSubject = "Joe's 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) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendPlain:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendLegacyPlainWithTracking(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+	m.SetTracking(true)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendPlainWithTracking:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendLegacyPlainAt(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+	m.SetDeliveryTime(time.Now().Add(5 * time.Minute))
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendPlainAt:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendLegacyHtml(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mailgun.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+	m.SetHtml(exampleHtml)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendHtml:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendLegacyTracking(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mailgun.NewMessage(fromUser, exampleSubject, exampleText+"Tracking!\n", toUser)
+	m.SetTracking(false)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendTracking:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendLegacyTag(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mailgun.NewMessage(fromUser, exampleSubject, exampleText+"Tags Galore!\n", toUser)
+	m.AddTag("FooTag")
+	m.AddTag("BarTag")
+	m.AddTag("BlortTag")
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendTag:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendLegacyMIME(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	m := mailgun.NewMIMEMessage(ioutil.NopCloser(strings.NewReader(exampleMime)), toUser)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendMIME:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestGetStoredMessage(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	id, err := findStoredMessageID(mg) // somehow...
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// First, get our stored message.
+	msg, err := mg.GetStoredMessage(id)
+	if err != nil {
+		t.Fatal(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.
+	err = mg.DeleteStoredMessage(id)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+// Tries to locate the first stored event type, returning the associated stored message key.
+func findStoredMessageID(mg mailgun.Mailgun) (string, error) {
+	ei := mg.NewEventIterator()
+	err := ei.GetFirstPage(mailgun.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) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendPlain:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendMGPlainWithTracking(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+	m.SetTracking(true)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendPlainWithTracking:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendMGPlainAt(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+	m.SetDeliveryTime(time.Now().Add(5 * time.Minute))
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendPlainAt:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendMGHtml(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
+	m.SetHtml(exampleHtml)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendHtml:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendMGTracking(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	m := mg.NewMessage(fromUser, exampleSubject, exampleText+"Tracking!\n", toUser)
+	m.SetTracking(false)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendTracking:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendMGTag(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	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)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendTag:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendMGMIME(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	m := mg.NewMIMEMessage(ioutil.NopCloser(strings.NewReader(exampleMime)), toUser)
+	msg, id, err := mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println("TestSendMIME:MSG(" + msg + "),ID(" + id + ")")
+}
+
+func TestSendMGBatchFailRecipients(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	m := mg.NewMessage(fromUser, exampleSubject, exampleText+"Batch\n")
+	for i := 0; i < mailgun.MaxNumberOfRecipients; i++ {
+		m.AddRecipient("")	// We expect this to indicate a failure at the API
+	}
+	err := m.AddRecipientAndVariables(toUser, nil)
+	if err == nil {
+		// If we're here, either the SDK didn't send the message,
+		// OR the API didn't check for empty To: headers.
+		t.Fatal("Expected to fail!!")
+	}
+}
+
+func TestSendMGBatchRecipientVariables(t *testing.T) {
+	toUser := reqEnv(t, "MG_EMAIL_TO")
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	m := mg.NewMessage(fromUser, exampleSubject, templateText)
+	err := m.AddRecipientAndVariables(toUser, map[string]interface{}{
+		"name": "Joe Cool Example",
+		"table": 42,
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, _, err = mg.Send(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/routes_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/routes_test.go
new file mode 100644
index 0000000..3763b99
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/routes_test.go
@@ -0,0 +1,87 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"github.com/mailgun/mailgun-go"
+	"testing"
+)
+
+func TestRouteCRUD(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+
+	var countRoutes = func() int {
+		count, _, err := mg.GetRoutes(mailgun.DefaultLimit, mailgun.DefaultSkip)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return count
+	}
+
+	routeCount := countRoutes()
+
+	newRoute, err := mg.CreateRoute(mailgun.Route{
+		Priority:    1,
+		Description: "Sample Route",
+		Expression:  "match_recipient(\".*@samples.mailgun.org\")",
+		Actions: []string{
+			"forward(\"http://example.com/messages/\")",
+			"stop()",
+		},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if newRoute.ID == "" {
+		t.Fatal("I expected the route created to have an ID associated with it.")
+	}
+	defer func() {
+		err = mg.DeleteRoute(newRoute.ID)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		newCount := countRoutes()
+		if newCount != routeCount {
+			t.Fatalf("Expected %d routes defined; got %d", routeCount, newCount)
+		}
+	}()
+
+	newCount := countRoutes()
+	if newCount <= routeCount {
+		t.Fatalf("Expected %d routes defined; got %d", routeCount+1, newCount)
+	}
+
+	theRoute, err := mg.GetRouteByID(newRoute.ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if ((newRoute.Priority) != (theRoute.Priority)) ||
+		((newRoute.Description) != (theRoute.Description)) ||
+		((newRoute.Expression) != (theRoute.Expression)) ||
+		(len(newRoute.Actions) != len(theRoute.Actions)) ||
+		((newRoute.CreatedAt) != (theRoute.CreatedAt)) ||
+		((newRoute.ID) != (theRoute.ID)) {
+		t.Fatalf("Expected %#v, got %#v", newRoute, theRoute)
+	}
+	for i, action := range newRoute.Actions {
+		if action != theRoute.Actions[i] {
+			t.Fatalf("Expected %#v, got %#v", newRoute, theRoute)
+		}
+	}
+
+	changedRoute, err := mg.UpdateRoute(newRoute.ID, mailgun.Route{
+		Priority: 2,
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if changedRoute.Priority != 2 {
+		t.Fatalf("Expected a priority of 2; got %d", changedRoute.Priority)
+	}
+	if len(changedRoute.Actions) != 2 {
+		t.Fatalf("Expected actions to not be touched; got %d entries now", len(changedRoute.Actions))
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/spam_complaints_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/spam_complaints_test.go
new file mode 100644
index 0000000..676d8a1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/spam_complaints_test.go
@@ -0,0 +1,71 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"github.com/mailgun/mailgun-go"
+	"testing"
+)
+
+func TestGetComplaints(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	n, complaints, err := mg.GetComplaints(-1, -1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(complaints) != n {
+		t.Fatalf("Expected %d complaints; got %d", n, len(complaints))
+	}
+}
+
+func TestGetComplaintFromBazNoComplaint(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	publicApiKey := reqEnv(t, "MG_PUBLIC_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, publicApiKey)
+	_, err := mg.GetSingleComplaint("baz@example.com")
+	if err == nil {
+		t.Fatal("Expected not-found error for missing complaint")
+	}
+	ure, ok := err.(*mailgun.UnexpectedResponseError)
+	if !ok {
+		t.Fatal("Expected UnexpectedResponseError")
+	}
+	if ure.Actual != 404 {
+		t.Fatalf("Expected 404 response code; got %d", ure.Actual)
+	}
+}
+
+func TestCreateDeleteComplaint(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	var check = func(count int) {
+		c, _, err := mg.GetComplaints(mailgun.DefaultLimit, mailgun.DefaultSkip)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if c != count {
+			t.Fatalf("Expected baz@example.com to have %d complaints; got %d", count, c)
+		}
+	}
+
+	check(0)
+
+	err := mg.CreateComplaint("baz@example.com")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	check(1)
+
+	err = mg.DeleteComplaint("baz@example.com")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	check(0)
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/stats_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/stats_test.go
new file mode 100644
index 0000000..cc47e96
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/stats_test.go
@@ -0,0 +1,40 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"fmt"
+	mailgun "github.com/mailgun/mailgun-go"
+	"os"
+	"testing"
+	"text/tabwriter"
+)
+
+func TestGetStats(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+
+	totalCount, stats, err := mg.GetStats(-1, -1, nil, "sent", "opened")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fmt.Printf("Total Count: %d\n", totalCount)
+	tw := tabwriter.NewWriter(os.Stdout, 2, 8, 2, ' ', tabwriter.AlignRight)
+	fmt.Fprintf(tw, "Id\tEvent\tCreatedAt\tTotalCount\t\n")
+	for _, stat := range stats {
+		fmt.Fprintf(tw, "%s\t%s\t%s\t%d\t\n", stat.Id, stat.Event, stat.CreatedAt, stat.TotalCount)
+	}
+	tw.Flush()
+}
+
+func TestDeleteTag(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	err := mg.DeleteTag("newsletter")
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/unsubscribes_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/unsubscribes_test.go
new file mode 100644
index 0000000..cef7229
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/unsubscribes_test.go
@@ -0,0 +1,71 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"fmt"
+	mailgun "github.com/mailgun/mailgun-go"
+	"os"
+	"testing"
+	"text/tabwriter"
+)
+
+func TestGetUnsubscribes(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	n, us, err := mg.GetUnsubscribes(mailgun.DefaultLimit, mailgun.DefaultSkip)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Printf("Received %d out of %d unsubscribe records.\n", len(us), n)
+	if len(us) > 0 {
+		tw := &tabwriter.Writer{}
+		tw.Init(os.Stdout, 2, 8, 2, ' ', 0)
+		fmt.Fprintln(tw, "ID\tAddress\tCreated At\tTag\t")
+		for _, u := range us {
+			fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t\n", u.ID, u.Address, u.CreatedAt, u.Tag)
+		}
+		tw.Flush()
+	}
+}
+
+func TestGetUnsubscriptionByAddress(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	email := reqEnv(t, "MG_EMAIL_ADDR")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+	n, us, err := mg.GetUnsubscribesByAddress(email)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Printf("Received %d out of %d unsubscribe records.\n", len(us), n)
+	if len(us) > 0 {
+		tw := &tabwriter.Writer{}
+		tw.Init(os.Stdout, 2, 8, 2, ' ', 0)
+		fmt.Fprintln(tw, "ID\tAddress\tCreated At\tTag\t")
+		for _, u := range us {
+			fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t\n", u.ID, u.Address, u.CreatedAt, u.Tag)
+		}
+		tw.Flush()
+	}
+}
+
+func TestCreateDestroyUnsubscription(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	email := reqEnv(t, "MG_EMAIL_ADDR")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+
+	// Create unsubscription record
+	err := mg.Unsubscribe(email, "*")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Destroy the unsubscription record
+	err = mg.RemoveUnsubscribe(email)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/webhooks_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/webhooks_test.go
new file mode 100644
index 0000000..1663d7a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/acceptance/webhooks_test.go
@@ -0,0 +1,66 @@
+// +build acceptance
+
+package acceptance
+
+import (
+	"github.com/mailgun/mailgun-go"
+	"testing"
+)
+
+func TestWebhookCRUD(t *testing.T) {
+	domain := reqEnv(t, "MG_DOMAIN")
+	apiKey := reqEnv(t, "MG_API_KEY")
+	mg := mailgun.NewMailgun(domain, apiKey, "")
+
+	var countHooks = func() int {
+		hooks, err := mg.GetWebhooks()
+		if err != nil {
+			t.Fatal(err)
+		}
+		return len(hooks)
+	}
+
+	hookCount := countHooks()
+
+	err := mg.CreateWebhook("deliver", "http://www.example.com")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		err = mg.DeleteWebhook("deliver")
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		newCount := countHooks()
+		if newCount != hookCount {
+			t.Fatalf("Expected %d routes defined; got %d", hookCount, newCount)
+		}
+	}()
+
+	newCount := countHooks()
+	if newCount <= hookCount {
+		t.Fatalf("Expected %d routes defined; got %d", hookCount+1, newCount)
+	}
+
+	theURL, err := mg.GetWebhookByType("deliver")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if theURL != "http://www.example.com" {
+		t.Fatalf("Expected http://www.example.com, got %#v", theURL)
+	}
+
+	err = mg.UpdateWebhook("deliver", "http://api.example.com")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	hooks, err := mg.GetWebhooks()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if hooks["deliver"] != "http://api.example.com" {
+		t.Fatalf("Expected http://api.example.com, got %#v", hooks["deliver"])
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/bounces.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/bounces.go
new file mode 100644
index 0000000..f12076a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/bounces.go
@@ -0,0 +1,122 @@
+package mailgun
+
+import (
+	"github.com/mbanzon/simplehttp"
+	"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 bounceEnvelope struct {
+	TotalCount int      `json:"total_count"`
+	Items      []Bounce `json:"items"`
+}
+
+type singleBounceEnvelope struct {
+	Bounce Bounce `json:"bounce"`
+}
+
+// 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 := simplehttp.NewHTTPRequest(generateApiUrl(m, bouncesEndpoint))
+	if limit != -1 {
+		r.AddParameter("limit", strconv.Itoa(limit))
+	}
+	if skip != -1 {
+		r.AddParameter("skip", strconv.Itoa(skip))
+	}
+
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+
+	var response bounceEnvelope
+	err := getResponseFromJSON(r, &response)
+	if err != nil {
+		return -1, nil, err
+	}
+
+	return response.TotalCount, 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 := simplehttp.NewHTTPRequest(generateApiUrl(m, bouncesEndpoint) + "/" + address)
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+
+	var response singleBounceEnvelope
+	err := getResponseFromJSON(r, &response)
+	return response.Bounce, 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 := simplehttp.NewHTTPRequest(generateApiUrl(m, bouncesEndpoint))
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+
+	payload := simplehttp.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 := simplehttp.NewHTTPRequest(generateApiUrl(m, bouncesEndpoint) + "/" + address)
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+	_, err := makeDeleteRequest(r)
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/campaigns.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/campaigns.go
new file mode 100644
index 0000000..c90d0c5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/campaigns.go
@@ -0,0 +1,79 @@
+package mailgun
+
+import (
+	"github.com/mbanzon/simplehttp"
+)
+
+// 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 := simplehttp.NewHTTPRequest(generateApiUrl(m, campaignsEndpoint))
+	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 := simplehttp.NewHTTPRequest(generateApiUrl(m, campaignsEndpoint))
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+
+	payload := simplehttp.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 := simplehttp.NewHTTPRequest(generateApiUrl(m, campaignsEndpoint) + "/" + oldId)
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+
+	payload := simplehttp.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 := simplehttp.NewHTTPRequest(generateApiUrl(m, campaignsEndpoint) + "/" + id)
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+	_, err := makeDeleteRequest(r)
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/credentials.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/credentials.go
new file mode 100644
index 0000000..f5be9ef
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/credentials.go
@@ -0,0 +1,76 @@
+package mailgun
+
+import (
+	"fmt"
+	"github.com/mbanzon/simplehttp"
+	"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 := simplehttp.NewHTTPRequest(generateCredentialsUrl(mg, ""))
+	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 := simplehttp.NewHTTPRequest(generateCredentialsUrl(mg, ""))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generateCredentialsUrl(mg, id))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generateCredentialsUrl(mg, id))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	_, err := makeDeleteRequest(r)
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/domains.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/domains.go
new file mode 100644
index 0000000..2807174
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/domains.go
@@ -0,0 +1,121 @@
+package mailgun
+
+import (
+	"github.com/mbanzon/simplehttp"
+	"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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(domainsEndpoint))
+	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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(domainsEndpoint) + "/" + domain)
+	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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(domainsEndpoint))
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+
+	payload := simplehttp.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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(domainsEndpoint) + "/" + name)
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+	_, err := makeDeleteRequest(r)
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/email_validation.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/email_validation.go
new file mode 100644
index 0000000..918453a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/email_validation.go
@@ -0,0 +1,71 @@
+package mailgun
+
+import (
+	"github.com/mbanzon/simplehttp"
+	"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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(addressValidateEndpoint))
+	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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(addressParseEndpoint))
+	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
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/events.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/events.go
new file mode 100644
index 0000000..833a2b4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/events.go
@@ -0,0 +1,130 @@
+package mailgun
+
+import (
+	"fmt"
+	"github.com/mbanzon/simplehttp"
+	"time"
+)
+
+// Events are open-ended, loosely-defined JSON documents.
+// They will always have an event and a timestamp field, however.
+type Event map[string]interface{}
+
+// 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 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 string
+	mg               Mailgun
+}
+
+// NewEventIterator creates a new iterator for events.
+// Use GetFirstPage to retrieve the first batch of events.
+// Use Next and Previous thereafter as appropriate to iterate through sets of data.
+func (mg *MailgunImpl) NewEventIterator() *EventIterator {
+	return &EventIterator{mg: mg}
+}
+
+// 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 := simplehttp.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)
+}
+
+// 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 := simplehttp.NewHTTPRequest(url)
+	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"]
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/examples_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/examples_test.go
new file mode 100644
index 0000000..a4af5bf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/examples_test.go
@@ -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)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailgun.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailgun.go
new file mode 100644
index 0000000..490b739
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailgun.go
@@ -0,0 +1,291 @@
+// 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 (
+	"fmt"
+	"github.com/mbanzon/simplehttp"
+	"io"
+	"time"
+)
+
+const (
+	apiBase                 = "https://api.mailgun.net/v2"
+	messagesEndpoint        = "messages"
+	mimeMessagesEndpoint    = "messages.mime"
+	addressValidateEndpoint = "address/validate"
+	addressParseEndpoint    = "address/parse"
+	bouncesEndpoint         = "bounces"
+	statsEndpoint           = "stats"
+	domainsEndpoint         = "domains"
+	deleteTagEndpoint       = "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 {
+	Domain() string
+	ApiKey() string
+	PublicApiKey() string
+	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)
+	DeleteTag(tag string) error
+	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
+	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
+	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
+}
+
+// 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 {
+	domain       string
+	apiKey       string
+	publicApiKey string
+}
+
+// NewMailGun creates a new client instance.
+func NewMailgun(domain, apiKey, publicApiKey string) Mailgun {
+	m := MailgunImpl{domain: domain, apiKey: apiKey, publicApiKey: publicApiKey}
+	return &m
+}
+
+// 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
+}
+
+// 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", 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(endpoint, address string) string {
+	return fmt.Sprintf("%s/%s/%s/members", 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", 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(endpoint string) string {
+	return fmt.Sprintf("%s/%s", apiBase, endpoint)
+}
+
+// generateParameterizedUrl works as generateApiUrl, but supports query parameters.
+func generateParameterizedUrl(m Mailgun, endpoint string, payload simplehttp.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")
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailgun_test.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailgun_test.go
new file mode 100644
index 0000000..8abc3ac
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailgun_test.go
@@ -0,0 +1,68 @@
+package mailgun
+
+import (
+	"strconv"
+	"testing"
+)
+
+const domain = "valid-mailgun-domain"
+const apiKey = "valid-mailgun-api-key"
+const publicApiKey = "valid-mailgun-public-api-key"
+
+func TestMailgun(t *testing.T) {
+	m := NewMailgun(domain, apiKey, publicApiKey)
+
+	if domain != m.Domain() {
+		t.Fatal("Domain not equal!")
+	}
+
+	if apiKey != m.ApiKey() {
+		t.Fatal("ApiKey not equal!")
+	}
+
+	if publicApiKey != m.PublicApiKey() {
+		t.Fatal("PublicApiKey not equal!")
+	}
+}
+
+func TestBounceGetCode(t *testing.T) {
+	b1 := &Bounce{
+		CreatedAt: "blah",
+		code:      123,
+		Address:   "blort",
+		Error:     "bletch",
+	}
+	c, err := b1.GetCode()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if c != 123 {
+		t.Fatal("Expected 123; got ", c)
+	}
+
+	b2 := &Bounce{
+		CreatedAt: "blah",
+		code:      "456",
+		Address:   "blort",
+		Error:     "Bletch",
+	}
+	c, err = b2.GetCode()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if c != 456 {
+		t.Fatal("Expected 456; got ", c)
+	}
+
+	b3 := &Bounce{
+		CreatedAt: "blah",
+		code:      "456H",
+		Address:   "blort",
+		Error:     "Bletch",
+	}
+	c, err = b3.GetCode()
+	e, ok := err.(*strconv.NumError)
+	if !ok && e != nil {
+		t.Fatal("Expected a syntax error in numeric conversion: got ", err)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailing_lists.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailing_lists.go
new file mode 100644
index 0000000..a640a47
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/mailing_lists.go
@@ -0,0 +1,307 @@
+package mailgun
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/mbanzon/simplehttp"
+	"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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(listsEndpoint))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(listsEndpoint))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(listsEndpoint) + "/" + addr)
+	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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(listsEndpoint) + "/" + addr)
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	response, err := makeGetRequest(r)
+	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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(listsEndpoint) + "/" + addr)
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generateMemberApiUrl(listsEndpoint, addr))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generateMemberApiUrl(listsEndpoint, l) + "/" + s)
+	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 := simplehttp.NewHTTPRequest(generateMemberApiUrl(listsEndpoint, addr))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generateMemberApiUrl(listsEndpoint, l) + "/" + s)
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generateMemberApiUrl(listsEndpoint, addr) + "/" + member)
+	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.
+// s indicates the default subscribed status (Subscribed or Unsubscribed).
+// 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(s *bool, addr string, newMembers []interface{}) error {
+	r := simplehttp.NewHTTPRequest(generateMemberApiUrl(listsEndpoint, addr) + ".json")
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.NewFormDataPayload()
+	if s != nil {
+		p.AddValue("subscribed", yesNo(*s))
+	}
+	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
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/messages.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/messages.go
new file mode 100644
index 0000000..650502c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/messages.go
@@ -0,0 +1,647 @@
+package mailgun
+
+import (
+	"encoding/json"
+	"errors"
+	"io"
+	"time"
+
+	"github.com/mbanzon/simplehttp"
+)
+
+// 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
+
+	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]interface{} `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(*simplehttp.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)
+}
+
+// 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)
+}
+
+// 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 := simplehttp.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)
+			}
+		}
+
+		r := simplehttp.NewHTTPRequest(generateApiUrl(m, message.specific.endpoint()))
+		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 *simplehttp.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 *simplehttp.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 == "" {
+		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 := simplehttp.NewHTTPRequest(url)
+	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 := simplehttp.NewHTTPRequest(url)
+	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 := simplehttp.NewHTTPRequest(url)
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	_, err := makeDeleteRequest(r)
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/rest_shim.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/rest_shim.go
new file mode 100644
index 0000000..29279e0
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/rest_shim.go
@@ -0,0 +1,152 @@
+package mailgun
+
+import (
+	"fmt"
+	"github.com/mbanzon/simplehttp"
+)
+
+// 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
+}
+
+// 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", e.URL, e.Expected, e.Actual)
+}
+
+// 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 int) error {
+	return &UnexpectedResponseError{
+		URL:      url,
+		Expected: expected,
+		Actual:   got,
+	}
+}
+
+// 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 *simplehttp.HTTPRequest, kind string, p simplehttp.Payload) (*simplehttp.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.Code)
+	}
+	return rsp, err
+}
+
+// getResponseFromJSON shim performs a GET request, checking for a positive outcome.
+// See simplehttp.GetResponseFromJSON for more details.
+func getResponseFromJSON(r *simplehttp.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.Code)
+	}
+	return response.ParseFromJSON(v)
+}
+
+// postResponseFromJSON shim performs a POST request, checking for a positive outcome.
+// See simplehttp.PostResponseFromJSON for more details.
+func postResponseFromJSON(r *simplehttp.HTTPRequest, p simplehttp.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.Code)
+	}
+	return response.ParseFromJSON(v)
+}
+
+// putResponseFromJSON shim performs a PUT request, checking for a positive outcome.
+// See simplehttp.PutResponseFromJSON for more details.
+func putResponseFromJSON(r *simplehttp.HTTPRequest, p simplehttp.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.Code)
+	}
+	return response.ParseFromJSON(v)
+}
+
+// makeGetRequest shim performs a GET request, checking for a positive outcome.
+// See simplehttp.MakeGetRequest for more details.
+func makeGetRequest(r *simplehttp.HTTPRequest) (*simplehttp.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.Code)
+	}
+	return rsp, err
+}
+
+// makePostRequest shim performs a POST request, checking for a positive outcome.
+// See simplehttp.MakePostRequest for more details.
+func makePostRequest(r *simplehttp.HTTPRequest, p simplehttp.Payload) (*simplehttp.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.Code)
+	}
+	return rsp, err
+}
+
+// makePutRequest shim performs a PUT request, checking for a positive outcome.
+// See simplehttp.MakePutRequest for more details.
+func makePutRequest(r *simplehttp.HTTPRequest, p simplehttp.Payload) (*simplehttp.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.Code)
+	}
+	return rsp, err
+}
+
+// makeDeleteRequest shim performs a DELETE request, checking for a positive outcome.
+// See simplehttp.MakeDeleteRequest for more details.
+func makeDeleteRequest(r *simplehttp.HTTPRequest) (*simplehttp.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.Code)
+	}
+	return rsp, err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/routes.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/routes.go
new file mode 100644
index 0000000..3fde098
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/routes.go
@@ -0,0 +1,127 @@
+package mailgun
+
+import (
+	"github.com/mbanzon/simplehttp"
+	"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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(routesEndpoint))
+	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      []Route `json:"items"`
+	}
+	err := getResponseFromJSON(r, &envelope)
+	if err != nil {
+		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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(routesEndpoint))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(routesEndpoint) + "/" + id)
+	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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(routesEndpoint) + "/" + id)
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	var envelope struct {
+		Message string `json:"message"`
+		*Route  `json:"route"`
+	}
+	err := getResponseFromJSON(r, &envelope)
+	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 := simplehttp.NewHTTPRequest(generatePublicApiUrl(routesEndpoint) + "/" + id)
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/spam_complaints.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/spam_complaints.go
new file mode 100644
index 0000000..48d1cf3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/spam_complaints.go
@@ -0,0 +1,78 @@
+package mailgun
+
+import (
+	"github.com/mbanzon/simplehttp"
+	"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 {
+	TotalCount int         `json:"total_count"`
+	Items      []Complaint `json:"items"`
+}
+
+// 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 := simplehttp.NewHTTPRequest(generateApiUrl(m, complaintsEndpoint))
+	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 envelope.TotalCount, 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 := simplehttp.NewHTTPRequest(generateApiUrl(m, complaintsEndpoint) + "/" + address)
+	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 := simplehttp.NewHTTPRequest(generateApiUrl(m, complaintsEndpoint))
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generateApiUrl(m, complaintsEndpoint) + "/" + address)
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+	_, err := makeDeleteRequest(r)
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/stats.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/stats.go
new file mode 100644
index 0000000..8cccf3c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/stats.go
@@ -0,0 +1,59 @@
+package mailgun
+
+import (
+	"github.com/mbanzon/simplehttp"
+	"strconv"
+	"time"
+)
+
+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 := simplehttp.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(time.RFC3339))
+	}
+
+	for _, e := range event {
+		r.AddParameter("event", e)
+	}
+	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
+	}
+}
+
+// DeleteTag removes all counters for a particular tag, including the tag itself.
+func (m *MailgunImpl) DeleteTag(tag string) error {
+	r := simplehttp.NewHTTPRequest(generateApiUrl(m, deleteTagEndpoint) + "/" + tag)
+	r.SetBasicAuth(basicAuthUser, m.ApiKey())
+	_, err := makeDeleteRequest(r)
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/unsubscribes.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/unsubscribes.go
new file mode 100644
index 0000000..b296908
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/unsubscribes.go
@@ -0,0 +1,66 @@
+package mailgun
+
+import (
+	"github.com/mbanzon/simplehttp"
+	"strconv"
+)
+
+type Unsubscription struct {
+	CreatedAt string `json:"created_at"`
+	Tag       string `json:"tag"`
+	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 := simplehttp.NewHTTPRequest(generateApiUrl(mg, unsubscribesEndpoint))
+	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      []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 := simplehttp.NewHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, a))
+	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 := simplehttp.NewHTTPRequest(generateApiUrl(mg, unsubscribesEndpoint))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, a))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	_, err := makeDeleteRequest(r)
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mailgun/mailgun-go/webhooks.go b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/webhooks.go
new file mode 100644
index 0000000..1b4ab61
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mailgun/mailgun-go/webhooks.go
@@ -0,0 +1,68 @@
+package mailgun
+
+import (
+	"github.com/mbanzon/simplehttp"
+)
+
+// 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 := simplehttp.NewHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
+	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 := simplehttp.NewHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.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 := simplehttp.NewHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + t)
+	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 := simplehttp.NewHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + t)
+	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 := simplehttp.NewHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + t)
+	r.SetBasicAuth(basicAuthUser, mg.ApiKey())
+	p := simplehttp.NewUrlEncodedPayload()
+	p.AddValue("url", u)
+	_, err := makePutRequest(r, p)
+	return err
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/.gitignore b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/.gitignore
new file mode 100644
index 0000000..2b1c9a5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/.gitignore
@@ -0,0 +1 @@
+desktop.ini
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/.travis.yml b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/.travis.yml
new file mode 100644
index 0000000..296b871
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/.travis.yml
@@ -0,0 +1,11 @@
+language: go
+go:
+  - 1.3
+  - 1.4
+env:
+  - GOARCH=amd64
+  - GOARCH=386
+script:
+  - go get github.com/mbanzon/callbackenv
+  - go get github.com/mbanzon/dummyserver
+  - go test
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/LICENSE b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/LICENSE
new file mode 100644
index 0000000..74f74ee
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/LICENSE
@@ -0,0 +1,27 @@
+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 name of the {organization} 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.
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/README.md b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/README.md
new file mode 100644
index 0000000..cb032cf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/README.md
@@ -0,0 +1,15 @@
+simplehttp
+==========
+
+[![Build Status](https://travis-ci.org/mbanzon/simplehttp.png?branch=master)](https://travis-ci.org/mbanzon/simplehttp)
+
+Simple HTTP library for Go.
+
+This small library adds some utility functions for doing HTTP request and easilly gettings results as
+structs from JSON and XML.
+
+Supports alternative `http.Client` instances to support use on Google App Engine.
+
+Examples are coming soon.
+
+The code is released under a 3-clause BSD license. See the LICENSE file for more information.
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/helpers.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/helpers.go
new file mode 100644
index 0000000..64e2b87
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/helpers.go
@@ -0,0 +1,17 @@
+package simplehttp
+
+func (r *HTTPRequest) GetResponseFromJSON(v interface{}) error {
+	response, err := r.MakeGetRequest()
+	if err != nil {
+		return err
+	}
+	return response.ParseFromJSON(v)
+}
+
+func (r *HTTPRequest) PostResponseFromJSON(payload Payload, v interface{}) error {
+	response, err := r.MakePostRequest(payload)
+	if err != nil {
+		return err
+	}
+	return response.ParseFromJSON(v)
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/helpers_test.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/helpers_test.go
new file mode 100644
index 0000000..ecf3dc2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/helpers_test.go
@@ -0,0 +1,133 @@
+package simplehttp
+
+import (
+	"encoding/json"
+	"encoding/xml"
+	"testing"
+)
+
+func TestParsingGetFromJson(t *testing.T) {
+	tmp := testStruct{
+		Value1: "1",
+		Value2: "2",
+		Value3: "3",
+	}
+
+	data, err := json.Marshal(tmp)
+	if err != nil {
+		t.Fail()
+	}
+
+	server.SetNextResponse(data)
+
+	request := NewHTTPRequest(dummyurl)
+	var retVal testStruct
+	err = request.GetResponseFromJSON(&retVal)
+
+	if err != nil {
+		t.Fail()
+	}
+
+	if tmp.Value1 != retVal.Value1 {
+		t.Fail()
+	}
+
+	if tmp.Value2 != retVal.Value2 {
+		t.Fail()
+	}
+
+	if tmp.Value3 != retVal.Value3 {
+		t.Fail()
+	}
+}
+
+func TestFailingParsingGetFromJson(t *testing.T) {
+	request := NewHTTPRequest(invalidurl)
+	var retVal testStruct
+	err := request.GetResponseFromJSON(&retVal)
+
+	if err == nil {
+		t.Fail()
+	}
+}
+
+func TestParsingPostFromJson(t *testing.T) {
+	tmp := testStruct{
+		Value1: "1",
+		Value2: "2",
+		Value3: "3",
+	}
+
+	data, err := json.Marshal(tmp)
+	if err != nil {
+		t.Fail()
+	}
+
+	server.SetNextResponse(data)
+
+	request := NewHTTPRequest(dummyurl)
+	var retVal testStruct
+	err = request.PostResponseFromJSON(nil, &retVal)
+
+	if err != nil {
+		t.Fail()
+	}
+
+	if tmp.Value1 != retVal.Value1 {
+		t.Fail()
+	}
+
+	if tmp.Value2 != retVal.Value2 {
+		t.Fail()
+	}
+
+	if tmp.Value3 != retVal.Value3 {
+		t.Fail()
+	}
+}
+
+func TestFailingParsingPostFromJson(t *testing.T) {
+	request := NewHTTPRequest(invalidurl)
+	var retVal testStruct
+	err := request.PostResponseFromJSON(nil, &retVal)
+
+	if err == nil {
+		t.Fail()
+	}
+}
+
+func TestParsingGetFromXml(t *testing.T) {
+	tmp := testStruct{
+		Value1: "1",
+		Value2: "2",
+		Value3: "3",
+	}
+
+	data, err := xml.Marshal(tmp)
+	if err != nil {
+		t.Fail()
+	}
+
+	server.SetNextResponse(data)
+
+	request := NewHTTPRequest(dummyurl)
+	var retVal testStruct
+	response, err := request.MakeGetRequest()
+	response.ParseFromXML(&retVal)
+
+	if err != nil {
+		t.Fail()
+	}
+
+	if tmp.Value1 != retVal.Value1 {
+		t.Fail()
+	}
+
+	if tmp.Value2 != retVal.Value2 {
+		t.Fail()
+	}
+
+	if tmp.Value3 != retVal.Value3 {
+		t.Fail()
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/json_utils.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/json_utils.go
new file mode 100644
index 0000000..47ff4b9
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/json_utils.go
@@ -0,0 +1,31 @@
+package simplehttp
+
+import (
+	"encoding/json"
+	"net/http"
+)
+
+func GetJSONInput(r *http.Request, w http.ResponseWriter, v interface{}) (err error) {
+	decoder := json.NewDecoder(r.Body)
+	err = decoder.Decode(v)
+	if err != nil {
+		http.Error(w, "Bad request.", http.StatusBadRequest)
+		return err
+	}
+
+	return nil
+}
+
+func OutputJSON(w http.ResponseWriter, v interface{}) (err error) {
+	var data []byte
+	data, err = json.Marshal(v)
+	if err != nil {
+		http.Error(w, "Internal error.", http.StatusInternalServerError)
+		return err
+	}
+
+	w.Header().Add("Content-Type", "application/json")
+	_, err = w.Write(data)
+
+	return nil
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/parsing.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/parsing.go
new file mode 100644
index 0000000..0261ccf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/parsing.go
@@ -0,0 +1,15 @@
+package simplehttp
+
+import (
+	"encoding/json"
+	"encoding/xml"
+)
+
+// Parses the HTTPResponse as JSON to the given interface.
+func (r *HTTPResponse) ParseFromJSON(v interface{}) error {
+	return json.Unmarshal(r.Data, v)
+}
+
+func (r *HTTPResponse) ParseFromXML(v interface{}) error {
+	return xml.Unmarshal(r.Data, v)
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/payload_test.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/payload_test.go
new file mode 100644
index 0000000..7adcbfb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/payload_test.go
@@ -0,0 +1,38 @@
+package simplehttp
+
+import (
+	"bytes"
+	"github.com/mbanzon/callbackenv"
+	"io/ioutil"
+	"testing"
+)
+
+const (
+	FILE_ENV = "SIMPLEHTTP_TEST_FILE"
+)
+
+func TestFormDataPayloadPost(t *testing.T) {
+	payload := NewFormDataPayload()
+	payload.AddValue("key", "value")
+
+	buf := &bytes.Buffer{}
+	buf.Write([]byte("testing testing testing"))
+
+	rc := ioutil.NopCloser(buf)
+	payload.AddReadCloser("foo", "bar", rc)
+
+	callbackenv.RequireEnv(FILE_ENV,
+		func(file string) {
+			payload.AddFile("file", file)
+		}, nil)
+
+	request := NewHTTPRequest(dummyurl)
+	request.MakePostRequest(payload)
+}
+
+func TestUrlEncodedPayloadPost(t *testing.T) {
+	payload := NewUrlEncodedPayload()
+	payload.AddValue("key", "value")
+	request := NewHTTPRequest(dummyurl)
+	request.MakePostRequest(payload)
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/payloads.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/payloads.go
new file mode 100644
index 0000000..2c3fa2d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/payloads.go
@@ -0,0 +1,141 @@
+package simplehttp
+
+import (
+	"bytes"
+	"io"
+	"mime/multipart"
+	"net/url"
+	"os"
+	"path"
+)
+
+type keyValuePair struct {
+	key   string
+	value string
+}
+
+type keyNameRC struct {
+	key   string
+	name  string
+	value io.ReadCloser
+}
+
+type Payload interface {
+	GetPayloadBuffer() (*bytes.Buffer, error)
+	GetContentType() string
+}
+
+type RawPayload struct {
+	Data []byte
+}
+
+type FormDataPayload struct {
+	contentType string
+	Values      []keyValuePair
+	Files       []keyValuePair
+	ReadClosers []keyNameRC
+}
+
+type UrlEncodedPayload struct {
+	Values []keyValuePair
+}
+
+func NewRawPayload(data []byte) *RawPayload {
+	return &RawPayload{Data: data}
+}
+
+func (r *RawPayload) GetPayloadBuffer() (*bytes.Buffer, error) {
+	data := &bytes.Buffer{}
+	c, err := data.Write(r.Data)
+	if c != len(r.Data) || err != nil {
+		return data, err
+	}
+	return data, nil
+}
+
+func (r *RawPayload) GetContentType() string {
+	return ""
+}
+
+func NewFormDataPayload() *FormDataPayload {
+	return &FormDataPayload{}
+}
+
+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 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"
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/shorthand.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/shorthand.go
new file mode 100644
index 0000000..2d0c62c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/shorthand.go
@@ -0,0 +1,74 @@
+package simplehttp
+
+// Type to encapsulate basic authentication for requests.
+type BasicAuthentication struct {
+	User     string
+	Password string
+}
+
+// Type to wrap requests.
+type Request struct {
+	Url            string
+	Authentication BasicAuthentication
+	UserAgent      string
+	Data           []byte
+}
+
+func createHttpRequest(req Request) *HTTPRequest {
+	r := NewHTTPRequest(req.Url)
+	if req.Authentication.User != "" {
+		r.SetBasicAuth(req.Authentication.User, req.Authentication.Password)
+	}
+	if req.UserAgent != "" {
+		r.AddHeader("User-Agent", req.UserAgent)
+	}
+	return r
+}
+
+func (r Request) Get() (int, []byte, error) {
+	req := createHttpRequest(r)
+	res, err := req.MakeGetRequest()
+	if err == nil {
+		return res.Code, res.Data, err
+	} else {
+		return -1, nil, err
+	}
+}
+
+func (r Request) Post() (int, []byte, error) {
+	req := createHttpRequest(r)
+	var payload Payload = nil
+	if r.Data != nil {
+		payload = NewRawPayload(r.Data)
+	}
+	res, err := req.MakePostRequest(payload)
+	if err == nil {
+		return res.Code, res.Data, err
+	} else {
+		return -1, nil, err
+	}
+}
+
+func (r Request) Put() (int, []byte, error) {
+	req := createHttpRequest(r)
+	var payload Payload = nil
+	if r.Data != nil {
+		payload = NewRawPayload(r.Data)
+	}
+	res, err := req.MakePutRequest(payload)
+	if err == nil {
+		return res.Code, res.Data, err
+	} else {
+		return -1, nil, err
+	}
+}
+
+func (r Request) Delete() (int, []byte, error) {
+	req := createHttpRequest(r)
+	res, err := req.MakeDeleteRequest()
+	if err == nil {
+		return res.Code, res.Data, err
+	} else {
+		return -1, nil, err
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/shorthand_test.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/shorthand_test.go
new file mode 100644
index 0000000..5968224
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/shorthand_test.go
@@ -0,0 +1,104 @@
+package simplehttp
+
+import (
+	"testing"
+)
+
+func TestShorthandFailingPayload(t *testing.T) {
+	Request{
+		Url:  dummyurl,
+		Data: nil,
+	}.Post()
+}
+
+func TestShorthandGet(t *testing.T) {
+	code, _, err := Request{
+		Url:       dummyurl,
+		UserAgent: "simplehttp go test",
+	}.Get()
+
+	if code == -1 || err != nil {
+		t.Fail()
+	}
+}
+
+func TestShorthandPost(t *testing.T) {
+	code, _, err := Request{
+		Url:       dummyurl,
+		Data:      []byte("foobar"),
+		UserAgent: "simplehttp go test",
+		Authentication: BasicAuthentication{
+			User:     "test",
+			Password: "test",
+		},
+	}.Post()
+
+	if code == -1 || err != nil {
+		t.Fail()
+	}
+}
+
+func TestShorthandPut(t *testing.T) {
+	code, _, err := Request{
+		Url:       dummyurl,
+		Data:      []byte("foobar"),
+		UserAgent: "simplehttp go test",
+	}.Put()
+
+	if code == -1 || err != nil {
+		t.Fail()
+	}
+}
+
+func TestShorthandDelete(t *testing.T) {
+	code, _, err := Request{
+		Url:       dummyurl,
+		UserAgent: "simplehttp go test",
+	}.Delete()
+
+	if code == -1 || err != nil {
+		t.Fail()
+	}
+}
+
+func TestFailingShorthandGet(t *testing.T) {
+	code, _, err := Request{
+		Url: invalidurl,
+	}.Get()
+
+	if code != -1 || err == nil {
+		t.Fail()
+	}
+}
+
+func TestFailingShorthandPost(t *testing.T) {
+	code, _, err := Request{
+		Url:  invalidurl,
+		Data: []byte("foobar"),
+	}.Post()
+
+	if code != -1 || err == nil {
+		t.Fail()
+	}
+}
+
+func TestFailingShorthandPut(t *testing.T) {
+	code, _, err := Request{
+		Url:  invalidurl,
+		Data: []byte("foobar"),
+	}.Put()
+
+	if code != -1 || err == nil {
+		t.Fail()
+	}
+}
+
+func TestFailingShorthandDelete(t *testing.T) {
+	code, _, err := Request{
+		Url: invalidurl,
+	}.Delete()
+
+	if code != -1 || err == nil {
+		t.Fail()
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/simplehttp.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/simplehttp.go
new file mode 100644
index 0000000..6ea097e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/simplehttp.go
@@ -0,0 +1,147 @@
+// Package simplehttp provides some simple methods and types to do
+// HTTP queries with form values and parameters easily - especially
+// if the returned result is expected to be JSON or XML.
+//
+// Author: Michael Banzon
+package simplehttp
+
+import (
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+)
+
+// Holds all information used to make a HTTP request.
+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
+}
+
+// Creates a new HTTPRequest instance.
+func NewHTTPRequest(url string) *HTTPRequest {
+	return &HTTPRequest{URL: url, Client: http.DefaultClient}
+}
+
+// Adds a parameter to the generated query string.
+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)
+}
+
+// Adds a header that will be sent with the HTTP request.
+func (r *HTTPRequest) AddHeader(name, value string) {
+	// hej
+	if r.Headers == nil {
+		r.Headers = make(map[string]string)
+	}
+	r.Headers[name] = value
+}
+
+// Sets username and password for basic authentication.
+func (r *HTTPRequest) SetBasicAuth(user, password string) {
+	r.BasicAuthUser = user
+	r.BasicAuthPassword = password
+}
+
+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)
+	}
+
+	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
+}
+
+// Generates the complete URL using GET parameters.
+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) SetClient(c *http.Client) {
+	r.Client = c
+}
diff --git a/Godeps/_workspace/src/github.com/mbanzon/simplehttp/simplehttp_test.go b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/simplehttp_test.go
new file mode 100644
index 0000000..ef46737
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mbanzon/simplehttp/simplehttp_test.go
@@ -0,0 +1,37 @@
+package simplehttp
+
+import (
+	"github.com/mbanzon/dummyserver"
+	"log"
+	"strconv"
+	"testing"
+)
+
+var (
+	server     *dummyserver.DummyServer
+	dummyurl   string
+	invalidurl string
+)
+
+type testStruct struct {
+	Value1 string `json:"value1" xml:"value1"`
+	Value2 string `json:"value2" xml:"value2"`
+	Value3 string `json:"value3" xml:"value3"`
+}
+
+func init() {
+	server = dummyserver.NewRandomServer()
+	go func() {
+		err := server.Start()
+		log.Fatal(err)
+	}()
+	dummyurl = "http://localhost:" + strconv.Itoa(server.GetPort()) + "/"
+	invalidurl = "invalid://invalid"
+}
+
+func TestAddParameters(t *testing.T) {
+	request := NewHTTPRequest(dummyurl)
+	request.AddParameter("p1", "v1")
+
+	request.MakeGetRequest()
+}
diff --git a/helpers.go b/helpers.go
index 377fd64..1c0d048 100644
--- a/helpers.go
+++ b/helpers.go
@@ -108,6 +108,9 @@ func getClaims(r *http.Request) Claims {
 	if con != nil {
 		claims = con.(Claims)
 	}
-	claims.Ref = r.Header.Get("Origin")
+	origin := r.Header.Get("Origin")
+	if origin != "" {
+		claims.Ref = origin
+	}
 	return claims
 }
diff --git a/main.go b/main.go
index 818203c..c3d8674 100644
--- a/main.go
+++ b/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"log"
 	"net/http"
@@ -13,12 +14,14 @@ import (
 	"github.com/jmoiron/modl"
 	"github.com/jmoiron/sqlx"
 	"github.com/lib/pq"
+	"github.com/mailgun/mailgun-go"
 )
 
 var (
 	DB                             = &modl.DbMap{Dialect: modl.PostgresDialect{}}
 	DBH           modl.SqlExecutor = DB
 	schemaDecoder                  = schema.NewDecoder()
+	mgAccts                        = make(map[string]mailgun.Mailgun)
 )
 
 func main() {
@@ -84,6 +87,22 @@ func main() {
 func cmdServe(c *cli.Context) {
 	var err error
 
+	// Set up Mailgun handlers:
+	// [{"ref":"hymenobacter","domain":"hymenobacter.info","public":"abc","private":"123"}]
+	type account struct {
+		Ref     string
+		Domain  string
+		Public  string
+		Private string
+	}
+	var accounts []account
+	json.Unmarshal([]byte(os.Getenv("ACCOUNT_KEYS")), &accounts)
+	log.Printf("Mailgun: %+v", accounts)
+
+	for _, a := range accounts {
+		mgAccts[a.Ref] = mailgun.NewMailgun(a.Domain, a.Private, a.Public)
+	}
+
 	addr := os.Getenv("PORT")
 	if addr == "" {
 		addr = "8901"
diff --git a/users.go b/users.go
index a5348a1..814a375 100644
--- a/users.go
+++ b/users.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/gorilla/mux"
 	"github.com/lib/pq"
+	"github.com/mailgun/mailgun-go"
 
 	"golang.org/x/crypto/bcrypt"
 )
@@ -136,7 +137,7 @@ func (u UserService) list(val *url.Values) (entity, *appError) {
 	sql := `SELECT id, email, 'password' AS password, name, role,
 		created_at, updated_at, deleted_at
 		FROM users
-		WHERE verified IS NOT FALSE
+		WHERE verified IS TRUE
 		AND deleted_at IS NULL;`
 	if err := DBH.Select(&users, sql); err != nil {
 		return nil, newJSONError(err, http.StatusInternalServerError)
@@ -150,7 +151,7 @@ func (u UserService) get(id int64, genus string) (entity, *appError) {
 		created_at, updated_at, deleted_at
 		FROM users
 		WHERE id=$1
-		AND verified IS NOT FALSE
+		AND verified IS TRUE
 		AND deleted_at IS NULL;`
 	if err := DBH.SelectOne(&user, q, id); err != nil {
 		if err == sql.ErrNoRows {
@@ -213,13 +214,36 @@ func (u UserService) create(e *entity, claims Claims) *appError {
 		return newJSONError(err, http.StatusInternalServerError)
 	}
 
+	// Send out confirmation email
+	mg, ok := mgAccts[claims.Ref]
+	if ok {
+		sender := fmt.Sprintf("%s Admin <admin@%s>", mg.Domain(), mg.Domain())
+		recipient := fmt.Sprintf("%s <%s>", user.Name, user.Email)
+		subject := fmt.Sprintf("New Account Confirmation - %s", mg.Domain())
+		message := fmt.Sprintf("You are receiving this message because this email "+
+			"address was used to sign up for an account at %s. Please visit this "+
+			"URL to complete the sign up process: %s/users/new/verify/%s. If you "+
+			"did not request an account, please disregard this message.",
+			mg.Domain(), claims.Ref, nonce)
+		m := mailgun.NewMessage(sender, subject, message, recipient)
+		_, _, err := mg.Send(m)
+		if err != nil {
+			log.Printf("%+v\n", err)
+			return newJSONError(err, http.StatusInternalServerError)
+		}
+	}
+
 	return nil
 }
 
 // for thermokarst/jwt: authentication callback
 func dbAuthenticate(email string, password string) error {
 	var user User
-	q := `SELECT * FROM users WHERE lower(email)=lower($1);`
+	q := `SELECT *
+		FROM users
+		WHERE lower(email)=lower($1)
+		AND verified IS TRUE
+		AND deleted_at IS NULL;`
 	if err := DBH.SelectOne(&user, q, email); err != nil {
 		return ErrInvalidEmailOrPassword
 	}
@@ -232,7 +256,11 @@ func dbAuthenticate(email string, password string) error {
 // for thermokarst/jwt: setting user in claims bundle
 func dbGetUserByEmail(email string) (*User, error) {
 	var user User
-	q := `SELECT * FROM users WHERE lower(email)=lower($1);`
+	q := `SELECT *
+		FROM users
+		WHERE lower(email)=lower($1)
+		AND verified IS TRUE
+		AND deleted_at IS NULL;`
 	if err := DBH.SelectOne(&user, q, email); err != nil {
 		if err == sql.ErrNoRows {
 			return nil, ErrUserNotFound