Repository: mailgun/mailgun-go (go)

Issue:
API base URL cannot be changed
The base API URL seems to be pointed to the US API currently, with no indication that you need to change it to point to EU if your domain is set there. Instead, you're met with a 404 error telling you your domain is not found.

When attempting to call `SetAPIBase()` to set the URL to the EU API, you are unable to include the `https://` because you will get this error: `invalid character 'M' looking for beginning of value`. If you leave out the scheme thinking it will be set by default, you get a POST failure due to the missing scheme: `unsupported protocol scheme`.

**Edit:** Turns out the trailing `/v3` is essential for this to work. This is very confusing, and there doesn't seem to be any direct documentation telling you to switch region, or how to properly. Perhaps the `mailgun.NewMailgun()` should take a third, regional type to specify which API endpoint to be hitting?

Pull request:
Clarified how to set EU region
* Added consts `APIBaseEU` and `APIBaseUS` to help customers change regions
* Documented how to change regions in the README
* Fixes #197 

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index 2206075..ab66201 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,7 +4,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [3.6.1-rc.1] - 2019-06-26
+## [3.6.1-rc.3] - 2019-07-16
+### Added
+* APIBaseEU and APIBaseUS to help customers change regions
+* Documented how to change regions in the README
+
+## [3.6.1-rc.2] - 2019-07-01
 ### Changes
 * Fix the JSON response for `GetMember()`
 * Typo in format string in max number of tags error
diff --git a/README.md b/README.md
index 995889f..04b3654 100644
--- a/README.md
+++ b/README.md
@@ -260,6 +260,13 @@ The official mailgun documentation includes examples using this library. Go
 [here](https://documentation.mailgun.com/en/latest/api_reference.html#api-reference)
 and click on the "Go" button at the top of the page.
 
+### EU Region
+European customers will need to change the default API Base to access your domains
+
+```go
+mg := mailgun.NewMailgun("your-domain.com", "private-api-key")
+mg.SetAPIBase(mailgun.APIBaseEU)
+```
 ## Installation
 
 If you are using [golang modules](https://github.com/golang/go/wiki/Modules) make sure you
diff --git a/httphelpers.go b/httphelpers.go
index 515b8b9..a74adc6 100644
--- a/httphelpers.go
+++ b/httphelpers.go
@@ -13,9 +13,12 @@ import (
 	"net/url"
 	"os"
 	"path"
+	"regexp"
 	"strings"
 )
 
+var validURL = regexp.MustCompile(`^/v[2-4].*`)
+
 type httpRequest struct {
 	URL               string
 	Parameters        map[string][]string
@@ -291,6 +294,11 @@ func (r *httpRequest) generateUrlWithParameters() (string, error) {
 	if err != nil {
 		return "", err
 	}
+
+	if !validURL.MatchString(url.Path) {
+		return "", errors.New(`BaseAPI must end with a /v2, /v3 or /v4; setBaseAPI("https://host/v3")`)
+	}
+
 	q := url.Query()
 	if r.Parameters != nil && len(r.Parameters) > 0 {
 		for name, values := range r.Parameters {
diff --git a/mailgun.go b/mailgun.go
index 9b3a378..9ea1e4e 100644
--- a/mailgun.go
+++ b/mailgun.go
@@ -91,6 +91,8 @@ var Debug = false
 const (
 	// Base Url the library uses to contact mailgun. Use SetAPIBase() to override
 	APIBase              = "https://api.mailgun.net/v3"
+	APIBaseUS            = APIBase
+	APIBaseEU            = "https://api.eu.mailgun.net/v3"
 	messagesEndpoint     = "messages"
 	mimeMessagesEndpoint = "messages.mime"
 	bouncesEndpoint      = "bounces"
@@ -299,6 +301,14 @@ func (mg *MailgunImpl) SetClient(c *http.Client) {
 }
 
 // SetAPIBase updates the API Base URL for this client.
+//  // For EU Customers
+//  mg.SetAPIBase(mailgun.APIBaseEU)
+//
+//  // For US Customers
+//  mg.SetAPIBase(mailgun.APIBaseUS)
+//
+//  // Set a custom base API
+//  mg.SetAPIBase("https://localhost/v3")
 func (mg *MailgunImpl) SetAPIBase(address string) {
 	mg.apiBase = address
 }


Test patch:
diff --git a/mailgun_test.go b/mailgun_test.go
index 27d0007..1024ff7 100644
--- a/mailgun_test.go
+++ b/mailgun_test.go
@@ -1,17 +1,19 @@
-package mailgun
+package mailgun_test
 
 import (
+	"context"
 	"net/http"
 	"testing"
 
 	"github.com/facebookgo/ensure"
+	"github.com/mailgun/mailgun-go/v3"
 )
 
 const domain = "valid-mailgun-domain"
 const apiKey = "valid-mailgun-api-key"
 
 func TestMailgun(t *testing.T) {
-	m := NewMailgun(domain, apiKey)
+	m := mailgun.NewMailgun(domain, apiKey)
 
 	ensure.DeepEqual(t, m.Domain(), domain)
 	ensure.DeepEqual(t, m.APIKey(), apiKey)
@@ -21,3 +23,13 @@ func TestMailgun(t *testing.T) {
 	m.SetClient(client)
 	ensure.DeepEqual(t, client, m.Client())
 }
+
+func TestInvalidBaseAPI(t *testing.T) {
+	mg := mailgun.NewMailgun(testDomain, testKey)
+	mg.SetAPIBase("https://localhost")
+
+	ctx := context.Background()
+	_, err := mg.GetDomain(ctx, "unknown.domain")
+	ensure.NotNil(t, err)
+	ensure.DeepEqual(t, err.Error(), `BaseAPI must end with a /v2, /v3 or /v4; setBaseAPI("https://host/v3")`)
+}
diff --git a/messages_test.go b/messages_test.go
index dec5df7..61f7009 100644
--- a/messages_test.go
+++ b/messages_test.go
@@ -220,7 +220,7 @@ func TestSendMGOffline(t *testing.T) {
 	)
 	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 		ensure.DeepEqual(t, req.Method, http.MethodPost)
-		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/%s/messages", exampleDomain))
+		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/v3/%s/messages", exampleDomain))
 		ensure.DeepEqual(t, req.FormValue("from"), fromUser)
 		ensure.DeepEqual(t, req.FormValue("subject"), exampleSubject)
 		ensure.DeepEqual(t, req.FormValue("text"), exampleText)
@@ -231,7 +231,7 @@ func TestSendMGOffline(t *testing.T) {
 	defer srv.Close()
 
 	mg := NewMailgun(exampleDomain, exampleAPIKey)
-	mg.SetAPIBase(srv.URL)
+	mg.SetAPIBase(srv.URL + "/v3")
 	ctx := context.Background()
 
 	m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
@@ -253,7 +253,7 @@ func TestSendMGSeparateDomain(t *testing.T) {
 	)
 	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 		ensure.DeepEqual(t, req.Method, http.MethodPost)
-		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/%s/messages", signingDomain))
+		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/v3/%s/messages", signingDomain))
 		ensure.DeepEqual(t, req.FormValue("from"), fromUser)
 		ensure.DeepEqual(t, req.FormValue("subject"), exampleSubject)
 		ensure.DeepEqual(t, req.FormValue("text"), exampleText)
@@ -264,7 +264,7 @@ func TestSendMGSeparateDomain(t *testing.T) {
 	defer srv.Close()
 
 	mg := NewMailgun(exampleDomain, exampleAPIKey)
-	mg.SetAPIBase(srv.URL)
+	mg.SetAPIBase(srv.URL + "/v3")
 
 	ctx := context.Background()
 	m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
@@ -295,7 +295,7 @@ func TestSendMGMessageVariables(t *testing.T) {
 	)
 	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 		ensure.DeepEqual(t, req.Method, http.MethodPost)
-		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/%s/messages", exampleDomain))
+		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/v3/%s/messages", exampleDomain))
 
 		ensure.DeepEqual(t, req.FormValue("from"), fromUser)
 		ensure.DeepEqual(t, req.FormValue("subject"), exampleSubject)
@@ -310,7 +310,7 @@ func TestSendMGMessageVariables(t *testing.T) {
 	defer srv.Close()
 
 	mg := NewMailgun(exampleDomain, exampleAPIKey)
-	mg.SetAPIBase(srv.URL)
+	mg.SetAPIBase(srv.URL + "/v3")
 
 	m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
 	m.AddVariable(exampleStrVarKey, exampleStrVarVal)
@@ -368,7 +368,7 @@ func TestSendEOFError(t *testing.T) {
 	defer srv.Close()
 
 	mg := NewMailgun(exampleDomain, exampleAPIKey)
-	mg.SetAPIBase(srv.URL)
+	mg.SetAPIBase(srv.URL + "/v3")
 
 	m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
 	_, _, err := mg.Send(context.Background(), m)
@@ -386,13 +386,13 @@ func TestHasRecipient(t *testing.T) {
 
 	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 		ensure.DeepEqual(t, req.Method, http.MethodPost)
-		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/%s/messages", exampleDomain))
+		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/v3/%s/messages", exampleDomain))
 		fmt.Fprint(w, `{"message":"Queued, Thank you", "id":"<20111114174239.25659.5820@samples.mailgun.org>"}`)
 	}))
 	defer srv.Close()
 
 	mg := NewMailgun(exampleDomain, exampleAPIKey)
-	mg.SetAPIBase(srv.URL)
+	mg.SetAPIBase(srv.URL + "/v3")
 
 	// No recipient
 	m := mg.NewMessage(fromUser, exampleSubject, exampleText)
@@ -423,7 +423,7 @@ func TestResendStored(t *testing.T) {
 	)
 	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 		ensure.DeepEqual(t, req.Method, http.MethodPost)
-		ensure.DeepEqual(t, req.URL.Path, "/some-url")
+		ensure.DeepEqual(t, req.URL.Path, "/v3/some-url")
 		ensure.DeepEqual(t, req.FormValue("to"), toUser)
 
 		rsp := fmt.Sprintf(`{"message":"%s", "id":"%s"}`, exampleMessage, exampleID)
@@ -432,13 +432,13 @@ func TestResendStored(t *testing.T) {
 	defer srv.Close()
 
 	mg := NewMailgun(exampleDomain, exampleAPIKey)
-	mg.SetAPIBase(srv.URL)
+	mg.SetAPIBase(srv.URL + "/v3")
 
-	msg, id, err := mg.ReSend(context.Background(), srv.URL+"/some-url")
+	msg, id, err := mg.ReSend(context.Background(), srv.URL+"/v3/some-url")
 	ensure.NotNil(t, err)
 	ensure.DeepEqual(t, err.Error(), "must provide at least one recipient")
 
-	msg, id, err = mg.ReSend(context.Background(), srv.URL+"/some-url", toUser)
+	msg, id, err = mg.ReSend(context.Background(), srv.URL+"/v3/some-url", toUser)
 	ensure.Nil(t, err)
 	ensure.DeepEqual(t, msg, exampleMessage)
 	ensure.DeepEqual(t, id, exampleID)
@@ -454,7 +454,7 @@ func TestSendTLSOptions(t *testing.T) {
 	)
 	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 		ensure.DeepEqual(t, req.Method, http.MethodPost)
-		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/%s/messages", exampleDomain))
+		ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/v3/%s/messages", exampleDomain))
 		ensure.DeepEqual(t, req.FormValue("from"), fromUser)
 		ensure.DeepEqual(t, req.FormValue("subject"), exampleSubject)
 		ensure.DeepEqual(t, req.FormValue("text"), exampleText)
@@ -467,7 +467,7 @@ func TestSendTLSOptions(t *testing.T) {
 	defer srv.Close()
 
 	mg := NewMailgun(exampleDomain, exampleAPIKey)
-	mg.SetAPIBase(srv.URL)
+	mg.SetAPIBase(srv.URL + "/v3")
 	ctx := context.Background()
 
 	m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
@@ -497,7 +497,7 @@ func TestSendTemplate(t *testing.T) {
 	defer srv.Close()
 
 	mg := NewMailgun(exampleDomain, exampleAPIKey)
-	mg.SetAPIBase(srv.URL)
+	mg.SetAPIBase(srv.URL + "/v3")
 	ctx := context.Background()
 
 	m := mg.NewMessage(fromUser, exampleSubject, "", toUser)