Repository: mailgun/mailgun-go
Pull request #388 (2025-02-09 11:59:24)

DE-1417 Switch domains to v4


Patch:
diff --git a/domain_keys.go b/domain_keys.go
new file mode 100644
index 0000000..71930b0
--- /dev/null
+++ b/domain_keys.go
@@ -0,0 +1,17 @@
+package mailgun
+
+import (
+	"context"
+)
+
+// UpdateDomainDkimSelector updates the DKIM selector for a domain
+func (mg *MailgunImpl) UpdateDomainDkimSelector(ctx context.Context, domain, dkimSelector string) error {
+	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/dkim_selector")
+	r.setClient(mg.HTTPClient())
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+
+	payload := newUrlEncodedPayload()
+	payload.addValue("dkim_selector", dkimSelector)
+	_, err := makePutRequest(ctx, r, payload)
+	return err
+}
diff --git a/domains.go b/domains.go
index 5e35e71..6ed4c4e 100644
--- a/domains.go
+++ b/domains.go
@@ -8,11 +8,11 @@ import (
 
 // Use these to specify a spam action when creating a new domain.
 const (
-	// Tag the received message with headers providing a measure of its spamness.
+	// SpamActionTag tags the received message with headers providing a measure of its spamness.
 	SpamActionTag = SpamAction("tag")
-	// Prevents Mailgun from taking any action on what it perceives to be spam.
+	// SpamActionDisabled prevents Mailgun from taking any action on what it perceives to be spam.
 	SpamActionDisabled = SpamAction("disabled")
-	// instructs Mailgun to just block or delete the message all-together.
+	// SpamActionDelete instructs Mailgun to just block or delete the message all-together.
 	SpamActionDelete = SpamAction("delete")
 )
 
@@ -20,27 +20,37 @@ type SpamAction string
 
 // A Domain structure holds information about a domain used when sending mail.
 type Domain struct {
-	CreatedAt    RFC2822Time `json:"created_at"`
-	SMTPLogin    string      `json:"smtp_login"`
-	Name         string      `json:"name"`
-	SMTPPassword string      `json:"smtp_password"`
-	Wildcard     bool        `json:"wildcard"`
-	SpamAction   SpamAction  `json:"spam_action"`
-	State        string      `json:"state"`
-	WebScheme    string      `json:"web_scheme"`
+	CreatedAt                  RFC2822Time `json:"created_at"`
+	ID                         string      `json:"id"`
+	IsDisabled                 bool        `json:"is_disabled"`
+	Name                       string      `json:"name"`
+	RequireTLS                 bool        `json:"require_tls"`
+	SkipVerification           bool        `json:"skip_verification"`
+	SMTPLogin                  string      `json:"smtp_login"`
+	SMTPPassword               string      `json:"smtp_password,omitempty"`
+	SpamAction                 SpamAction  `json:"spam_action"`
+	State                      string      `json:"state"`
+	Type                       string      `json:"type"`
+	TrackingHost               string      `json:"tracking_host,omitempty"`
+	UseAutomaticSenderSecurity bool        `json:"use_automatic_sender_security"`
+	WebPrefix                  string      `json:"web_prefix"`
+	WebScheme                  string      `json:"web_scheme"`
+	Wildcard                   bool        `json:"wildcard"`
 }
 
 // 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
+	Active     bool     `json:"is_active"`
+	Cached     []string `json:"cached"`
+	Name       string   `json:"name,omitempty"`
+	Priority   string   `json:"priority,omitempty"`
+	RecordType string   `json:"record_type"`
+	Valid      string   `json:"valid"`
+	Value      string   `json:"value"`
 }
 
-type DomainResponse struct {
+type GetDomainResponse struct {
 	Domain              Domain      `json:"domain"`
 	ReceivingDNSRecords []DNSRecord `json:"receiving_dns_records"`
 	SendingDNSRecords   []DNSRecord `json:"sending_dns_records"`
@@ -80,8 +90,12 @@ type domainTrackingResponse struct {
 	Tracking DomainTracking `json:"tracking"`
 }
 
+type ListDomainsOptions struct {
+	Limit int
+}
+
 // ListDomains retrieves a set of domains from Mailgun.
-func (mg *MailgunImpl) ListDomains(opts *ListOptions) *DomainsIterator {
+func (mg *MailgunImpl) ListDomains(opts *ListDomainsOptions) *DomainsIterator {
 	var limit int
 	if opts != nil {
 		limit = opts.Limit
@@ -92,7 +106,7 @@ func (mg *MailgunImpl) ListDomains(opts *ListOptions) *DomainsIterator {
 	}
 	return &DomainsIterator{
 		mg:                  mg,
-		url:                 generateApiUrl(mg, 3, domainsEndpoint),
+		url:                 generateApiUrl(mg, 4, domainsEndpoint),
 		domainsListResponse: domainsListResponse{TotalCount: -1},
 		limit:               limit,
 	}
@@ -231,40 +245,32 @@ func (ri *DomainsIterator) fetch(ctx context.Context, skip, limit int) error {
 	return getResponseFromJSON(ctx, r, &ri.domainsListResponse)
 }
 
+type GetDomainOptions struct{}
+
 // GetDomain retrieves detailed information about the named domain.
-func (mg *MailgunImpl) GetDomain(ctx context.Context, domain string) (DomainResponse, error) {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain)
+func (mg *MailgunImpl) GetDomain(ctx context.Context, domain string, _ *GetDomainOptions) (GetDomainResponse, error) {
+	r := newHTTPRequest(generateApiUrl(mg, 4, domainsEndpoint) + "/" + domain)
 	r.setClient(mg.HTTPClient())
 	r.setBasicAuth(basicAuthUser, mg.APIKey())
-	var resp DomainResponse
+	var resp GetDomainResponse
 	err := getResponseFromJSON(ctx, r, &resp)
 	return resp, err
 }
 
-func (mg *MailgunImpl) VerifyDomain(ctx context.Context, domain string) (string, error) {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/verify")
-	r.setClient(mg.HTTPClient())
-	r.setBasicAuth(basicAuthUser, mg.APIKey())
-
-	payload := newUrlEncodedPayload()
-	var resp DomainResponse
-	err := putResponseFromJSON(ctx, r, payload, &resp)
-	return resp.Domain.State, err
-}
-
-// VerifyAndReturnDomain verifies & retrieves detailed information about the named domain.
-func (mg *MailgunImpl) VerifyAndReturnDomain(ctx context.Context, domain string) (DomainResponse, error) {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/verify")
+func (mg *MailgunImpl) VerifyDomain(ctx context.Context, domain string) (GetDomainResponse, error) {
+	r := newHTTPRequest(generateApiUrl(mg, 4, domainsEndpoint) + "/" + domain + "/verify")
 	r.setClient(mg.HTTPClient())
 	r.setBasicAuth(basicAuthUser, mg.APIKey())
 
 	payload := newUrlEncodedPayload()
-	var resp DomainResponse
+	var resp GetDomainResponse
 	err := putResponseFromJSON(ctx, r, payload, &resp)
 	return resp, err
 }
 
-// Optional parameters when creating a domain
+// CreateDomainOptions - optional parameters when creating a domain
+// https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Domains/#tag/Domains/operation/POST-v4-domains
+// TODO(vtopc): support all fields
 type CreateDomainOptions struct {
 	Password           string
 	SpamAction         SpamAction
@@ -281,8 +287,8 @@ type CreateDomainOptions struct {
 // 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 (mg *MailgunImpl) CreateDomain(ctx context.Context, name string, opts *CreateDomainOptions) (DomainResponse, error) {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint))
+func (mg *MailgunImpl) CreateDomain(ctx context.Context, name string, opts *CreateDomainOptions) (GetDomainResponse, error) {
+	r := newHTTPRequest(generateApiUrl(mg, 4, domainsEndpoint))
 	r.setClient(mg.HTTPClient())
 	r.setBasicAuth(basicAuthUser, mg.APIKey())
 
@@ -309,37 +315,14 @@ func (mg *MailgunImpl) CreateDomain(ctx context.Context, name string, opts *Crea
 			payload.addValue("smtp_password", opts.Password)
 		}
 		if opts.WebScheme != "" {
-			payload.addValue("web_scheme", string(opts.WebScheme))
+			payload.addValue("web_scheme", opts.WebScheme)
 		}
 	}
-	var resp DomainResponse
+	var resp GetDomainResponse
 	err := postResponseFromJSON(ctx, r, payload, &resp)
 	return resp, err
 }
 
-// GetDomainConnection returns delivery connection settings for the defined domain
-func (mg *MailgunImpl) GetDomainConnection(ctx context.Context, domain string) (DomainConnection, error) {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/connection")
-	r.setClient(mg.HTTPClient())
-	r.setBasicAuth(basicAuthUser, mg.APIKey())
-	var resp domainConnectionResponse
-	err := getResponseFromJSON(ctx, r, &resp)
-	return resp.Connection, err
-}
-
-// UpdateDomainConnection updates the specified delivery connection settings for the defined domain
-func (mg *MailgunImpl) UpdateDomainConnection(ctx context.Context, domain string, settings DomainConnection) error {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/connection")
-	r.setClient(mg.HTTPClient())
-	r.setBasicAuth(basicAuthUser, mg.APIKey())
-
-	payload := newUrlEncodedPayload()
-	payload.addValue("require_tls", boolToString(settings.RequireTLS))
-	payload.addValue("skip_verification", boolToString(settings.SkipVerification))
-	_, err := makePutRequest(ctx, r, payload)
-	return err
-}
-
 // DeleteDomain instructs Mailgun to dispose of the named domain name
 func (mg *MailgunImpl) DeleteDomain(ctx context.Context, name string) error {
 	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + name)
@@ -349,86 +332,16 @@ func (mg *MailgunImpl) DeleteDomain(ctx context.Context, name string) error {
 	return err
 }
 
-// GetDomainTracking returns tracking settings for a domain
-func (mg *MailgunImpl) GetDomainTracking(ctx context.Context, domain string) (DomainTracking, error) {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/tracking")
-	r.setClient(mg.HTTPClient())
-	r.setBasicAuth(basicAuthUser, mg.APIKey())
-	var resp domainTrackingResponse
-	err := getResponseFromJSON(ctx, r, &resp)
-	return resp.Tracking, err
-}
-
-func (mg *MailgunImpl) UpdateClickTracking(ctx context.Context, domain, active string) error {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/tracking/click")
-	r.setClient(mg.HTTPClient())
-	r.setBasicAuth(basicAuthUser, mg.APIKey())
-
-	payload := newUrlEncodedPayload()
-	payload.addValue("active", active)
-	_, err := makePutRequest(ctx, r, payload)
-	return err
-}
-
-func (mg *MailgunImpl) UpdateUnsubscribeTracking(ctx context.Context, domain, active, htmlFooter, textFooter string) error {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/tracking/unsubscribe")
-	r.setClient(mg.HTTPClient())
-	r.setBasicAuth(basicAuthUser, mg.APIKey())
-
-	payload := newUrlEncodedPayload()
-	payload.addValue("active", active)
-	payload.addValue("html_footer", htmlFooter)
-	payload.addValue("text_footer", textFooter)
-	_, err := makePutRequest(ctx, r, payload)
-	return err
-}
-
-func (mg *MailgunImpl) UpdateOpenTracking(ctx context.Context, domain, active string) error {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/tracking/open")
-	r.setClient(mg.HTTPClient())
-	r.setBasicAuth(basicAuthUser, mg.APIKey())
-
-	payload := newUrlEncodedPayload()
-	payload.addValue("active", active)
-	_, err := makePutRequest(ctx, r, payload)
-	return err
-}
-
-// Update the DKIM selector for a domain
-func (mg *MailgunImpl) UpdateDomainDkimSelector(ctx context.Context, domain, dkimSelector string) error {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/dkim_selector")
-	r.setClient(mg.HTTPClient())
-	r.setBasicAuth(basicAuthUser, mg.APIKey())
-
-	payload := newUrlEncodedPayload()
-	payload.addValue("dkim_selector", dkimSelector)
-	_, err := makePutRequest(ctx, r, payload)
-	return err
-}
-
-// UpdateDomainTrackingWebPrefix updates the CNAME used for tracking opens and clicks
-// Deprecated: Use UpdateDomainOptions use UpdateDomain
-// https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Domains/#tag/Domains/operation/PUT-v3-domains--name--web-prefix
-func (mg *MailgunImpl) UpdateDomainTrackingWebPrefix(ctx context.Context, domain, webPrefix string) error {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/web_prefix")
-	r.setClient(mg.HTTPClient())
-	r.setBasicAuth(basicAuthUser, mg.APIKey())
-
-	payload := newUrlEncodedPayload()
-	payload.addValue("web_prefix", webPrefix)
-	_, err := makePutRequest(ctx, r, payload)
-	return err
-}
-
 // UpdateDomainOptions options for updating a domain
 type UpdateDomainOptions struct {
 	WebScheme string
+	WebPrefix string
 }
 
 // UpdateDomain updates a domain's attributes.
 // Currently only the web_scheme update is supported, spam_action and wildcard are to be added.
 func (mg *MailgunImpl) UpdateDomain(ctx context.Context, name string, opts *UpdateDomainOptions) error {
-	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + name)
+	r := newHTTPRequest(generateApiUrl(mg, 4, domainsEndpoint) + "/" + name)
 	r.setClient(mg.HTTPClient())
 	r.setBasicAuth(basicAuthUser, mg.APIKey())
 
@@ -436,7 +349,10 @@ func (mg *MailgunImpl) UpdateDomain(ctx context.Context, name string, opts *Upda
 
 	if opts != nil {
 		if opts.WebScheme != "" {
-			payload.addValue("web_scheme", string(opts.WebScheme))
+			payload.addValue("web_scheme", opts.WebScheme)
+		}
+		if opts.WebPrefix != "" {
+			payload.addValue("web_prefix", opts.WebScheme)
 		}
 	}
 
diff --git a/domains_connection.go b/domains_connection.go
new file mode 100644
index 0000000..6ecbb53
--- /dev/null
+++ b/domains_connection.go
@@ -0,0 +1,28 @@
+package mailgun
+
+import (
+	"context"
+)
+
+// GetDomainConnection returns delivery connection settings for the defined domain
+func (mg *MailgunImpl) GetDomainConnection(ctx context.Context, domain string) (DomainConnection, error) {
+	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/connection")
+	r.setClient(mg.HTTPClient())
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+	var resp domainConnectionResponse
+	err := getResponseFromJSON(ctx, r, &resp)
+	return resp.Connection, err
+}
+
+// UpdateDomainConnection updates the specified delivery connection settings for the defined domain
+func (mg *MailgunImpl) UpdateDomainConnection(ctx context.Context, domain string, settings DomainConnection) error {
+	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/connection")
+	r.setClient(mg.HTTPClient())
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+
+	payload := newUrlEncodedPayload()
+	payload.addValue("require_tls", boolToString(settings.RequireTLS))
+	payload.addValue("skip_verification", boolToString(settings.SkipVerification))
+	_, err := makePutRequest(ctx, r, payload)
+	return err
+}
diff --git a/domains_tracking.go b/domains_tracking.go
new file mode 100644
index 0000000..a0a029d
--- /dev/null
+++ b/domains_tracking.go
@@ -0,0 +1,50 @@
+package mailgun
+
+import (
+	"context"
+)
+
+// GetDomainTracking returns tracking settings for a domain
+func (mg *MailgunImpl) GetDomainTracking(ctx context.Context, domain string) (DomainTracking, error) {
+	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/tracking")
+	r.setClient(mg.HTTPClient())
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+	var resp domainTrackingResponse
+	err := getResponseFromJSON(ctx, r, &resp)
+	return resp.Tracking, err
+}
+
+func (mg *MailgunImpl) UpdateClickTracking(ctx context.Context, domain, active string) error {
+	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/tracking/click")
+	r.setClient(mg.HTTPClient())
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+
+	payload := newUrlEncodedPayload()
+	payload.addValue("active", active)
+	_, err := makePutRequest(ctx, r, payload)
+	return err
+}
+
+func (mg *MailgunImpl) UpdateUnsubscribeTracking(ctx context.Context, domain, active, htmlFooter, textFooter string) error {
+	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/tracking/unsubscribe")
+	r.setClient(mg.HTTPClient())
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+
+	payload := newUrlEncodedPayload()
+	payload.addValue("active", active)
+	payload.addValue("html_footer", htmlFooter)
+	payload.addValue("text_footer", textFooter)
+	_, err := makePutRequest(ctx, r, payload)
+	return err
+}
+
+func (mg *MailgunImpl) UpdateOpenTracking(ctx context.Context, domain, active string) error {
+	r := newHTTPRequest(generateApiUrl(mg, 3, domainsEndpoint) + "/" + domain + "/tracking/open")
+	r.setClient(mg.HTTPClient())
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+
+	payload := newUrlEncodedPayload()
+	payload.addValue("active", active)
+	_, err := makePutRequest(ctx, r, payload)
+	return err
+}
diff --git a/examples/examples.go b/examples/examples.go
index f04ab4c..6e1814b 100644
--- a/examples/examples.go
+++ b/examples/examples.go
@@ -28,7 +28,7 @@ func CreateComplaint(domain, apiKey string) error {
 	return mg.CreateComplaint(ctx, domain, "bob@example.com")
 }
 
-func AddDomain(domain, apiKey string) (mailgun.DomainResponse, error) {
+func AddDomain(domain, apiKey string) (mailgun.GetDomainResponse, error) {
 	mg := mailgun.NewMailgun(apiKey)
 
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
@@ -152,7 +152,7 @@ func CreateCredential(domain, apiKey string) error {
 	return mg.CreateCredential(ctx, domain, "alice@example.com", "secret")
 }
 
-func CreateDomain(domain, apiKey string) (mailgun.DomainResponse, error) {
+func CreateDomain(domain, apiKey string) (mailgun.GetDomainResponse, error) {
 	mg := mailgun.NewMailgun(apiKey)
 
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
@@ -417,13 +417,13 @@ func ListCredentials(domain, apiKey string) ([]mailgun.Credential, error) {
 	return result, nil
 }
 
-func GetDomain(domain, apiKey string) (mailgun.DomainResponse, error) {
+func GetDomain(domain, apiKey string) (mailgun.GetDomainResponse, error) {
 	mg := mailgun.NewMailgun(apiKey)
 
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 	defer cancel()
 
-	return mg.GetDomain(ctx, domain)
+	return mg.GetDomain(ctx, domain, nil)
 }
 
 func ListDomainIPS(domain, apiKey string) ([]mailgun.IPAddress, error) {
diff --git a/mailgun.go b/mailgun.go
index fd4cb03..13f0288 100644
--- a/mailgun.go
+++ b/mailgun.go
@@ -145,19 +145,22 @@ type Mailgun interface {
 	DeleteTag(ctx context.Context, domain, tag string) error
 	ListTags(domain string, opts *ListTagOptions) *TagIterator
 
-	ListDomains(opts *ListOptions) *DomainsIterator
-	GetDomain(ctx context.Context, domain string) (DomainResponse, error)
-	CreateDomain(ctx context.Context, name string, opts *CreateDomainOptions) (DomainResponse, error)
+	ListDomains(opts *ListDomainsOptions) *DomainsIterator
+	GetDomain(ctx context.Context, domain string, opts *GetDomainOptions) (GetDomainResponse, error)
+	CreateDomain(ctx context.Context, name string, opts *CreateDomainOptions) (GetDomainResponse, error)
 	DeleteDomain(ctx context.Context, name string) error
-	VerifyDomain(ctx context.Context, name string) (string, error)
-	VerifyAndReturnDomain(ctx context.Context, name string) (DomainResponse, error)
+	VerifyDomain(ctx context.Context, name string) (GetDomainResponse, error)
+
 	UpdateDomainConnection(ctx context.Context, domain string, dc DomainConnection) error
 	GetDomainConnection(ctx context.Context, domain string) (DomainConnection, error)
+
 	GetDomainTracking(ctx context.Context, domain string) (DomainTracking, error)
 	UpdateClickTracking(ctx context.Context, domain, active string) error
 	UpdateUnsubscribeTracking(ctx context.Context, domain, active, htmlFooter, textFooter string) error
 	UpdateOpenTracking(ctx context.Context, domain, active string) error
 
+	UpdateDomainDkimSelector(ctx context.Context, domain, dkimSelector string) error
+
 	GetStoredMessage(ctx context.Context, url string) (StoredMessage, error)
 	GetStoredMessageRaw(ctx context.Context, id string) (StoredMessageRaw, error)
 	GetStoredAttachment(ctx context.Context, url string) ([]byte, error)
diff --git a/mock.go b/mock.go
index 17c8326..e992085 100644
--- a/mock.go
+++ b/mock.go
@@ -121,7 +121,6 @@ func NewMockServer() MockServer {
 	r.Route("/v3", func(r chi.Router) {
 		ms.addIPRoutes(r)
 		ms.addExportRoutes(r)
-		ms.addDomainRoutes(r)
 		ms.addMailingListRoutes(r)
 		ms.addEventRoutes(r)
 		ms.addMessagesRoutes(r)
@@ -138,6 +137,7 @@ func NewMockServer() MockServer {
 	r.Route("/v5", func(r chi.Router) {
 		ms.addSubaccountRoutes(r)
 	})
+	ms.addDomainRoutes(r) // mix of v3 and v4
 	ms.addValidationRoutes(r)
 	ms.addAnalyticsRoutes(r)
 
diff --git a/mock_domains.go b/mock_domains.go
index f4f6845..74599e0 100644
--- a/mock_domains.go
+++ b/mock_domains.go
@@ -17,7 +17,6 @@ type DomainContainer struct {
 }
 
 func (ms *mockServer) addDomainRoutes(r chi.Router) {
-
 	ms.domainList = append(ms.domainList, DomainContainer{
 		Domain: Domain{
 			CreatedAt:    RFC2822Time(time.Now().UTC()),
@@ -82,25 +81,23 @@ func (ms *mockServer) addDomainRoutes(r chi.Router) {
 		},
 	})
 
-	r.Get("/domains", ms.listDomains)
-	r.Post("/domains", ms.createDomain)
-	r.Get("/domains/{domain}", ms.getDomain)
-	r.Put("/domains/{domain}", ms.updateDomain)
-	r.Put("/domains/{domain}/verify", ms.getDomain)
-	r.Delete("/domains/{domain}", ms.deleteDomain)
-	//r.Get("/domains/{domain}/credentials", ms.getCredentials)
-	//r.Post("/domains/{domain}/credentials", ms.createCredentials)
-	//r.Put("/domains/{domain}/credentials/{login}", ms.updateCredentials)
-	//r.Delete("/domains/{domain}/credentials/{login}", ms.deleteCredentials)
-	r.Get("/domains/{domain}/connection", ms.getConnection)
-	r.Put("/domains/{domain}/connection", ms.updateConnection)
-	r.Get("/domains/{domain}/tracking", ms.getTracking)
-	r.Put("/domains/{domain}/tracking/click", ms.updateClickTracking)
-	r.Put("/domains/{domain}/tracking/open", ms.updateOpenTracking)
-	r.Put("/domains/{domain}/tracking/unsubscribe", ms.updateUnsubTracking)
-	r.Get("/domains/{domain}/limits/tag", ms.getTagLimits)
-	r.Put("/domains/{domain}/dkim_selector", ms.updateDKIMSelector)
-	r.Put("/domains/{domain}/web_prefix", ms.updateWebPrefix)
+	r.Get("/v4/domains", ms.listDomains)
+	r.Post("/v4/domains", ms.createDomain)
+	r.Get("/v4/domains/{domain}", ms.getDomain)
+	r.Put("/v4/domains/{domain}", ms.updateDomain)
+	r.Put("/v4/domains/{domain}/verify", ms.getDomain)
+	r.Delete("/v3/domains/{domain}", ms.deleteDomain)
+
+	r.Get("/v3/domains/{domain}/connection", ms.getConnection)
+	r.Put("/v3/domains/{domain}/connection", ms.updateConnection)
+
+	r.Get("/v3/domains/{domain}/tracking", ms.getTracking)
+	r.Put("/v3/domains/{domain}/tracking/click", ms.updateClickTracking)
+	r.Put("/v3/domains/{domain}/tracking/open", ms.updateOpenTracking)
+	r.Put("/v3/domains/{domain}/tracking/unsubscribe", ms.updateUnsubTracking)
+	r.Get("/v3/domains/{domain}/limits/tag", ms.getTagLimits)
+
+	r.Put("/v3/domains/{domain}/dkim_selector", ms.updateDKIMSelector)
 }
 
 func (ms *mockServer) listDomains(w http.ResponseWriter, r *http.Request) {


Test patch:
diff --git a/domain_keys_test.go b/domain_keys_test.go
new file mode 100644
index 0000000..1f8e1a3
--- /dev/null
+++ b/domain_keys_test.go
@@ -0,0 +1,21 @@
+package mailgun_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/mailgun/mailgun-go/v4"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDomainDkimSelector(t *testing.T) {
+	mg := mailgun.NewMailgun(testKey)
+	err := mg.SetAPIBase(server.URL())
+	require.NoError(t, err)
+
+	ctx := context.Background()
+
+	// Update Domain DKIM selector
+	err = mg.UpdateDomainDkimSelector(ctx, testDomain, "gotest")
+	require.NoError(t, err)
+}
diff --git a/domains_connection_test.go b/domains_connection_test.go
new file mode 100644
index 0000000..386fbf4
--- /dev/null
+++ b/domains_connection_test.go
@@ -0,0 +1,32 @@
+package mailgun_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/mailgun/mailgun-go/v4"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDomainConnection(t *testing.T) {
+	mg := mailgun.NewMailgun(testKey)
+	err := mg.SetAPIBase(server.URL())
+	require.NoError(t, err)
+
+	ctx := context.Background()
+
+	info, err := mg.GetDomainConnection(ctx, testDomain)
+	require.NoError(t, err)
+
+	require.True(t, info.RequireTLS)
+	require.True(t, info.SkipVerification)
+
+	info.RequireTLS = false
+	err = mg.UpdateDomainConnection(ctx, testDomain, info)
+	require.NoError(t, err)
+
+	info, err = mg.GetDomainConnection(ctx, testDomain)
+	require.NoError(t, err)
+	require.False(t, info.RequireTLS)
+	require.True(t, info.SkipVerification)
+}
diff --git a/domains_test.go b/domains_test.go
index c7a4fb2..f0836bf 100644
--- a/domains_test.go
+++ b/domains_test.go
@@ -47,7 +47,7 @@ func TestGetSingleDomain(t *testing.T) {
 	require.True(t, it.Next(ctx, &page))
 	require.NoError(t, it.Err())
 
-	dr, err := mg.GetDomain(ctx, page[0].Name)
+	dr, err := mg.GetDomain(ctx, page[0].Name, nil)
 	require.NoError(t, err)
 	require.True(t, len(dr.ReceivingDNSRecords) != 0)
 	require.True(t, len(dr.SendingDNSRecords) != 0)
@@ -67,7 +67,7 @@ func TestGetSingleDomainNotExist(t *testing.T) {
 	require.NoError(t, err)
 
 	ctx := context.Background()
-	_, err = mg.GetDomain(ctx, "unknown.domain")
+	_, err = mg.GetDomain(ctx, "unknown.domain", nil)
 	if err == nil {
 		t.Fatal("Did not expect a domain to exist")
 	}
@@ -97,72 +97,6 @@ func TestAddUpdateDeleteDomain(t *testing.T) {
 	require.NoError(t, mg.DeleteDomain(ctx, "mx.mailgun.test"))
 }
 
-func TestDomainConnection(t *testing.T) {
-	mg := mailgun.NewMailgun(testKey)
-	err := mg.SetAPIBase(server.URL())
-	require.NoError(t, err)
-
-	ctx := context.Background()
-
-	info, err := mg.GetDomainConnection(ctx, testDomain)
-	require.NoError(t, err)
-
-	require.True(t, info.RequireTLS)
-	require.True(t, info.SkipVerification)
-
-	info.RequireTLS = false
-	err = mg.UpdateDomainConnection(ctx, testDomain, info)
-	require.NoError(t, err)
-
-	info, err = mg.GetDomainConnection(ctx, testDomain)
-	require.NoError(t, err)
-	require.False(t, info.RequireTLS)
-	require.True(t, info.SkipVerification)
-}
-
-func TestDomainTracking(t *testing.T) {
-	mg := mailgun.NewMailgun(testKey)
-	err := mg.SetAPIBase(server.URL())
-	require.NoError(t, err)
-
-	ctx := context.Background()
-
-	info, err := mg.GetDomainTracking(ctx, testDomain)
-	require.NoError(t, err)
-
-	require.False(t, info.Unsubscribe.Active)
-	require.True(t, info.Unsubscribe.HTMLFooter != "")
-	require.True(t, info.Unsubscribe.TextFooter != "")
-	require.True(t, info.Click.Active)
-	require.True(t, info.Open.Active)
-
-	// Click Tracking
-	err = mg.UpdateClickTracking(ctx, testDomain, "no")
-	require.NoError(t, err)
-
-	info, err = mg.GetDomainTracking(ctx, testDomain)
-	require.NoError(t, err)
-	require.False(t, info.Click.Active)
-
-	// Open Tracking
-	err = mg.UpdateOpenTracking(ctx, testDomain, "no")
-	require.NoError(t, err)
-
-	info, err = mg.GetDomainTracking(ctx, testDomain)
-	require.NoError(t, err)
-	require.False(t, info.Open.Active)
-
-	// Unsubscribe
-	err = mg.UpdateUnsubscribeTracking(ctx, testDomain, "yes", "<h2>Hi</h2>", "Hi")
-	require.NoError(t, err)
-
-	info, err = mg.GetDomainTracking(ctx, testDomain)
-	require.NoError(t, err)
-	assert.True(t, info.Unsubscribe.Active)
-	assert.Equal(t, "<h2>Hi</h2>", info.Unsubscribe.HTMLFooter)
-	assert.Equal(t, "Hi", info.Unsubscribe.TextFooter)
-}
-
 func TestDomainVerify(t *testing.T) {
 	mg := mailgun.NewMailgun(testKey)
 	err := mg.SetAPIBase(server.URL())
@@ -173,38 +107,3 @@ func TestDomainVerify(t *testing.T) {
 	_, err = mg.VerifyDomain(ctx, testDomain)
 	require.NoError(t, err)
 }
-
-func TestDomainVerifyAndReturn(t *testing.T) {
-	mg := mailgun.NewMailgun(testKey)
-	err := mg.SetAPIBase(server.URL())
-	require.NoError(t, err)
-
-	ctx := context.Background()
-
-	_, err = mg.VerifyAndReturnDomain(ctx, testDomain)
-	require.NoError(t, err)
-}
-
-func TestDomainDkimSelector(t *testing.T) {
-	mg := mailgun.NewMailgun(testKey)
-	err := mg.SetAPIBase(server.URL())
-	require.NoError(t, err)
-
-	ctx := context.Background()
-
-	// Update Domain DKIM selector
-	err = mg.UpdateDomainDkimSelector(ctx, testDomain, "gotest")
-	require.NoError(t, err)
-}
-
-func TestDomainTrackingWebPrefix(t *testing.T) {
-	mg := mailgun.NewMailgun(testKey)
-	err := mg.SetAPIBase(server.URL())
-	require.NoError(t, err)
-
-	ctx := context.Background()
-
-	// Update Domain Tracking Web Prefix
-	err = mg.UpdateDomainTrackingWebPrefix(ctx, testDomain, "gotest")
-	require.NoError(t, err)
-}
diff --git a/domains_tracking_test.go b/domains_tracking_test.go
new file mode 100644
index 0000000..2600c45
--- /dev/null
+++ b/domains_tracking_test.go
@@ -0,0 +1,68 @@
+package mailgun_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/mailgun/mailgun-go/v4"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDomainTracking(t *testing.T) {
+	mg := mailgun.NewMailgun(testKey)
+	err := mg.SetAPIBase(server.URL())
+	require.NoError(t, err)
+
+	ctx := context.Background()
+
+	info, err := mg.GetDomainTracking(ctx, testDomain)
+	require.NoError(t, err)
+
+	require.False(t, info.Unsubscribe.Active)
+	require.True(t, info.Unsubscribe.HTMLFooter != "")
+	require.True(t, info.Unsubscribe.TextFooter != "")
+	require.True(t, info.Click.Active)
+	require.True(t, info.Open.Active)
+
+	// Click Tracking
+	err = mg.UpdateClickTracking(ctx, testDomain, "no")
+	require.NoError(t, err)
+
+	info, err = mg.GetDomainTracking(ctx, testDomain)
+	require.NoError(t, err)
+	require.False(t, info.Click.Active)
+
+	// Open Tracking
+	err = mg.UpdateOpenTracking(ctx, testDomain, "no")
+	require.NoError(t, err)
+
+	info, err = mg.GetDomainTracking(ctx, testDomain)
+	require.NoError(t, err)
+	require.False(t, info.Open.Active)
+
+	// Unsubscribe
+	err = mg.UpdateUnsubscribeTracking(ctx, testDomain, "yes", "<h2>Hi</h2>", "Hi")
+	require.NoError(t, err)
+
+	info, err = mg.GetDomainTracking(ctx, testDomain)
+	require.NoError(t, err)
+	assert.True(t, info.Unsubscribe.Active)
+	assert.Equal(t, "<h2>Hi</h2>", info.Unsubscribe.HTMLFooter)
+	assert.Equal(t, "Hi", info.Unsubscribe.TextFooter)
+}
+
+func TestDomainTrackingWebPrefix(t *testing.T) {
+	mg := mailgun.NewMailgun(testKey)
+	err := mg.SetAPIBase(server.URL())
+	require.NoError(t, err)
+
+	ctx := context.Background()
+
+	// Update Domain Tracking Web Prefix
+	err = mg.UpdateDomain(ctx, testDomain, &mailgun.UpdateDomainOptions{
+		WebScheme: "",
+		WebPrefix: "gotest",
+	})
+	require.NoError(t, err)
+}
diff --git a/mailgun_test.go b/mailgun_test.go
index 6f2647b..c47541c 100644
--- a/mailgun_test.go
+++ b/mailgun_test.go
@@ -33,7 +33,7 @@ func TestInvalidBaseAPI(t *testing.T) {
 
 func TestValidBaseAPI(t *testing.T) {
 	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
-		var resp mailgun.DomainResponse
+		var resp mailgun.GetDomainResponse
 		b, err := json.Marshal(resp)
 		require.NoError(t, err)