Repository: mailgun/mailgun-go
Pull request #413 (2025-04-12 15:23:03)

DE-1526 Blocklist Monitoring (Domains)


Patch:
diff --git a/Makefile b/Makefile
index 9bf9cfc..55a4180 100644
--- a/Makefile
+++ b/Makefile
@@ -47,7 +47,9 @@ get-and-gen-models: get-openapi convert-openapi gen-models
 get-openapi:
 	cd $(TYPES_PATH) && git clone --depth 1 git@github.com:mailgun/redocly-mailgun.git
 
-## Convert openapi 3.1 to 3.0
+## Downgrade openapi 3.1 to 3.0
+# this is one of the official ways to support OpenAPI 3.1:
+# https://github.com/oapi-codegen/oapi-codegen?tab=readme-ov-file#does-oapi-codegen-support-openapi-31
 # install openapi-down-convert:
 #  npm i -g @apiture/openapi-down-convert
 .PHONY: convert-openapi
@@ -59,7 +61,7 @@ convert-openapi:
 
 # TODO(Go1.24): move into tools of go.mod(https://github.com/oapi-codegen/oapi-codegen?tab=readme-ov-file#for-go-124)?
 # install oapi-codegen:
-#  go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.4.2-0.20241128130830-b07f7ea6d520
+#  go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.4.2-0.20250511101814-8ea9351bbd3e
 #
 # ValidateEmailResponse is described here better, than in the OpenAPI documentation, so we are not generating it.
 # TODO(v6?): call gen-mailgun-models
@@ -70,17 +72,8 @@ gen-models: gen-inboxready-models
 .PHONY: gen-mailgun-models
 gen-mailgun-models:
 	oapi-codegen -config $(TYPES_PATH)/mailgun/codegen_cfg.yaml $(TYPES_PATH)/mailgun/openapi_3.0.yaml
-	# patch slices(`*[]` -> `[]`)
-	sed -i '' 's/\*\[\]/\[\]/' $(TYPES_PATH)/mailgun/model.gen.go
-	# patch maps(`*map` -> `map`)
-	sed -i '' 's/\*map/map/' $(TYPES_PATH)/mailgun/model.gen.go
 
 ## Generate Mailgun Optimize models
 .PHONY: gen-inboxready-models
 gen-inboxready-models:
 	oapi-codegen -config $(TYPES_PATH)/inboxready/codegen_cfg.yaml $(TYPES_PATH)/inboxready/openapi_3.0.yaml
-	# patch slices(`*[]` -> `[]`)
-	sed -i '' 's/\*\[\]/\[\]/' $(TYPES_PATH)/inboxready/model.gen.go
-	# patch maps(`*map` -> `map`)
-	sed -i '' 's/\*map/map/' $(TYPES_PATH)/inboxready/model.gen.go
-	gofmt -w $(TYPES_PATH)/inboxready/
diff --git a/inboxready_domains.go b/inboxready_domains.go
new file mode 100644
index 0000000..2380f69
--- /dev/null
+++ b/inboxready_domains.go
@@ -0,0 +1,117 @@
+package mailgun
+
+// https://documentation.mailgun.com/docs/inboxready/openapi-final/tag/Domains/
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/mailgun/errors"
+	"github.com/mailgun/mailgun-go/v5/internal/types/inboxready"
+	"github.com/mailgun/mailgun-go/v5/mtypes"
+)
+
+type ListMonitoredDomainsOptions = inboxready.GETV1InboxreadyDomainsParams
+
+type MonitoredDomainsIterator struct {
+	opts    ListMonitoredDomainsOptions
+	req     *httpRequest
+	resp    inboxready.InboxReadyGithubComMailgunInboxreadyAPIDomainListResponse
+	isFirst bool
+	err     error
+}
+
+func (mg *Client) ListMonitoredDomains(opts ListMonitoredDomainsOptions) (*MonitoredDomainsIterator, error) {
+	// TODO(vtopc): support opts.Domain
+
+	if opts.Limit == nil || *opts.Limit == 0 {
+		opts.Limit = ptr(10)
+	}
+
+	req := newHTTPRequest(generateApiUrl(mg, mtypes.InboxreadyDomainsVersion, mtypes.InboxreadyDomainsEndpoint))
+	req.addParameter("limit", strconv.Itoa(*opts.Limit))
+	req.setClient(mg.HTTPClient())
+	req.setBasicAuth(basicAuthUser, mg.APIKey())
+
+	return &MonitoredDomainsIterator{
+		opts:    opts,
+		req:     req,
+		isFirst: true,
+	}, nil
+}
+
+func (iter *MonitoredDomainsIterator) Err() error {
+	return iter.err
+}
+
+// Next retrieves the next page of items from the api. Returns false when there are
+// no more pages to retrieve or if there was an error.
+// Use `.Err()` to retrieve the error
+func (iter *MonitoredDomainsIterator) Next(ctx context.Context, resp []mtypes.MonitoredDomain) (more bool) {
+	if iter.err != nil {
+		return false
+	}
+
+	if iter.isFirst {
+		iter.isFirst = false
+	} else {
+		iter.req.URL = iter.resp.Paging.Next
+	}
+
+	v, err := iter.fetch(ctx)
+	if err != nil {
+		iter.err = err
+
+		return false
+	}
+
+	iter.resp = *v
+
+	return len(resp) == *iter.opts.Limit
+}
+
+func (iter *MonitoredDomainsIterator) fetch(ctx context.Context,
+) (*inboxready.InboxReadyGithubComMailgunInboxreadyAPIDomainListResponse, error) {
+	httpResp, err := makeGetRequest(ctx, iter.req)
+	if err != nil {
+		return nil, err
+	}
+
+	var resp inboxready.InboxReadyGithubComMailgunInboxreadyAPIDomainListResponse
+	err = httpResp.parseFromJSON(&resp)
+	if err != nil {
+		return nil, errors.Wrap(err, "decoding response")
+	}
+
+	return &resp, nil
+}
+
+// AddDomainToMonitoring adds a single domain to an account
+func (mg *Client) AddDomainToMonitoring(ctx context.Context, opts mtypes.AddDomainToMonitoringOptions,
+) (*mtypes.AddDomainToMonitoringResponse, error) {
+	req := newHTTPRequest(generateApiUrl(mg, mtypes.InboxreadyDomainsVersion, mtypes.InboxreadyDomainsEndpoint))
+	req.setBasicAuth(basicAuthUser, mg.APIKey())
+	req.setClient(mg.HTTPClient())
+
+	payload := newUrlEncodedPayload()
+	payload.addValue("domain", opts.Domain)
+
+	var resp mtypes.AddDomainToMonitoringResponse
+	if err := postResponseFromJSON(ctx, req, payload, &resp); err != nil {
+		return nil, err
+	}
+
+	return &resp, nil
+}
+
+// DeleteMonitoredDomain deletes a single domain from an account
+func (mg *Client) DeleteMonitoredDomain(ctx context.Context, opts mtypes.DeleteMonitoredDomainOptions,
+) error {
+	req := newHTTPRequest(generateApiUrl(mg, mtypes.InboxreadyDomainsVersion, mtypes.InboxreadyDomainsEndpoint))
+	req.setBasicAuth(basicAuthUser, mg.APIKey())
+	req.setClient(mg.HTTPClient())
+	req.addParameter("domain", opts.Domain)
+
+	_, err := makeDeleteRequest(ctx, req)
+	return err
+}
diff --git a/internal/types/inboxready/codegen_cfg.yaml b/internal/types/inboxready/codegen_cfg.yaml
index e4af234..dd87672 100644
--- a/internal/types/inboxready/codegen_cfg.yaml
+++ b/internal/types/inboxready/codegen_cfg.yaml
@@ -13,6 +13,7 @@ output-options:
     - Alerts
     - Domains # Blocklist Monitoring
     - IP Blocklist Monitoring
+  prefer-skip-optional-pointer-on-container-types: true
   name-normalizer: ToCamelCaseWithInitialisms
   additional-initialisms:
     - IDs
diff --git a/internal/types/inboxready/model.gen.go b/internal/types/inboxready/model.gen.go
index 484c04e..b2afa3b 100644
--- a/internal/types/inboxready/model.gen.go
+++ b/internal/types/inboxready/model.gen.go
@@ -1,6 +1,6 @@
 // Package inboxready provides primitives to interact with the openapi HTTP API.
 //
-// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.2-0.20241128130830-b07f7ea6d520 DO NOT EDIT.
+// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.2-0.20250511101814-8ea9351bbd3e DO NOT EDIT.
 package inboxready
 
 import (
@@ -221,6 +221,9 @@ type DELETEV1InboxreadyDomainsParams struct {
 
 // GETV1InboxreadyDomainsParams defines parameters for GETV1InboxreadyDomains.
 type GETV1InboxreadyDomainsParams struct {
+	// Limit Limit
+	Limit *int `form:"limit,omitempty" json:"limit,omitempty"`
+
 	// Domain Domain name
 	Domain *string `form:"domain,omitempty" json:"domain,omitempty"`
 }
@@ -240,7 +243,7 @@ type PUTV1InboxreadyDomainsVerifyParams struct {
 // GETV1InboxreadyIPAddressesParams defines parameters for GETV1InboxreadyIPAddresses.
 type GETV1InboxreadyIPAddressesParams struct {
 	// Limit Limit
-	Limit *string `form:"limit,omitempty" json:"limit,omitempty"`
+	Limit *int `form:"limit,omitempty" json:"limit,omitempty"`
 }
 
 // POSTV1AlertsEmailTestJSONRequestBody defines body for POSTV1AlertsEmailTest for application/json ContentType.
diff --git a/internal/types/mailgun/codegen_cfg.yaml b/internal/types/mailgun/codegen_cfg.yaml
index 877a9f7..a915afd 100644
--- a/internal/types/mailgun/codegen_cfg.yaml
+++ b/internal/types/mailgun/codegen_cfg.yaml
@@ -9,6 +9,7 @@ compatibility:
   disable-required-readonly-as-pointer: true
 
 output-options:
+  prefer-skip-optional-pointer-on-container-types: true
   name-normalizer: ToCamelCaseWithInitialisms
   additional-initialisms:
     - IDs
diff --git a/mailgun.go b/mailgun.go
index 07a7bc4..1d6a16c 100644
--- a/mailgun.go
+++ b/mailgun.go
@@ -96,8 +96,13 @@ const (
 	APIBaseUS = APIBase
 	APIBaseEU = "https://api.eu.mailgun.net"
 
+	OnBehalfOfHeader = "X-Mailgun-On-Behalf-Of"
+
 	basicAuthUser = "api"
+)
 
+// Mailgun Send endpoints
+const (
 	messagesEndpoint     = "messages"
 	mimeMessagesEndpoint = "messages.mime"
 	bouncesEndpoint      = "bounces"
@@ -114,8 +119,6 @@ const (
 	templatesEndpoint    = "templates"
 	accountsEndpoint     = "accounts"
 	subaccountsEndpoint  = "subaccounts"
-
-	OnBehalfOfHeader = "X-Mailgun-On-Behalf-Of"
 )
 
 // Mailgun defines the supported subset of the Mailgun API.
@@ -255,6 +258,8 @@ type Mailgun interface {
 	AddAlert(context.Context, mtypes.AlertsEventSettingRequest) (*mtypes.AlertsEventSettingResponse, error)
 	DeleteAlert(ctx context.Context, id uuid.UUID) error
 
+	ListMonitoredDomains(opts ListMonitoredDomainsOptions) (*MonitoredDomainsIterator, error)
+
 	ListSubaccounts(opts *ListSubaccountsOptions) *SubaccountsIterator
 	CreateSubaccount(ctx context.Context, subaccountName string) (mtypes.SubaccountResponse, error)
 	GetSubaccount(ctx context.Context, subaccountID string) (mtypes.SubaccountResponse, error)
diff --git a/mocks/mock.go b/mocks/mock.go
index 6d7bbdf..ab8f145 100644
--- a/mocks/mock.go
+++ b/mocks/mock.go
@@ -132,6 +132,7 @@ func NewServer() *Server {
 	ms.addValidationRoutes(r)
 	ms.addAnalyticsRoutes(r)
 	ms.addAlertsRoutes(r)
+	ms.addInboxreadyDomainsRoutes(r)
 
 	// Start the server
 	ms.srv = httptest.NewServer(r)
diff --git a/mocks/mock_alerts.go b/mocks/mock_alerts.go
index 772c4de..7d7d288 100644
--- a/mocks/mock_alerts.go
+++ b/mocks/mock_alerts.go
@@ -24,7 +24,7 @@ func (ms *Server) listAlerts(w http.ResponseWriter, _ *http.Request) {
 	resp := mtypes.AlertsSettingsResponse{
 		Events: []mtypes.AlertsEventSettingResponse{
 			{
-				Channel:   mtypes.EmailChannel,
+				Channel:   mtypes.AlertsEmailChannel,
 				EventType: "ip_listed",
 				ID:        ptr(uuid.New()),
 				Settings: mtypes.AlertsChannelSettings{
@@ -32,7 +32,7 @@ func (ms *Server) listAlerts(w http.ResponseWriter, _ *http.Request) {
 				},
 			},
 			{
-				Channel:   mtypes.WebhookChannel,
+				Channel:   mtypes.AlertsWebhookChannel,
 				EventType: "ip_delisted",
 				ID:        ptr(uuid.New()),
 				Settings: mtypes.AlertsChannelSettings{
diff --git a/mocks/mock_inboxready_domains.go b/mocks/mock_inboxready_domains.go
new file mode 100644
index 0000000..089ad84
--- /dev/null
+++ b/mocks/mock_inboxready_domains.go
@@ -0,0 +1,34 @@
+package mocks
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/go-chi/chi/v5"
+	"github.com/mailgun/mailgun-go/v5/internal/types/inboxready"
+	"github.com/mailgun/mailgun-go/v5/mtypes"
+)
+
+func (ms *Server) addInboxreadyDomainsRoutes(r chi.Router) {
+	r.Post(fmt.Sprintf("/v%d/%s", mtypes.InboxreadyDomainsVersion, mtypes.InboxreadyDomainsEndpoint), ms.addDomainToMonitoring)
+	// TODO(vtopc): add other routes
+}
+
+func (ms *Server) addDomainToMonitoring(w http.ResponseWriter, r *http.Request) {
+	resp := mtypes.AddDomainToMonitoringResponse{
+		Domain: inboxready.InboxReadyGithubComMailgunInboxreadyModelDomain{
+			CreatedAt: time.Now().Unix(),
+			Name:      r.FormValue("domain"),
+			Services: map[string]bool{
+				"service1": true,
+				"service2": true,
+			},
+			TxtRecord: "",
+			Verified:  inboxready.InboxReadyGithubComMailgunInboxreadyModelVerified{},
+		},
+		Message: "domain added",
+	}
+
+	toJSON(w, resp)
+}
diff --git a/mtypes/alerts.go b/mtypes/alerts.go
index 34245d3..145770c 100644
--- a/mtypes/alerts.go
+++ b/mtypes/alerts.go
@@ -12,9 +12,9 @@ const (
 
 // channels
 const (
-	EmailChannel   = "email"
-	WebhookChannel = "webhook"
-	SlackChannel   = "slack"
+	AlertsEmailChannel   = "email"
+	AlertsWebhookChannel = "webhook"
+	AlertsSlackChannel   = "slack"
 )
 
 type AlertsEventsResponse = inboxready.GithubComMailgunAlertsInternalAPIListAlertsEventsResp
diff --git a/mtypes/inboxready_domains.go b/mtypes/inboxready_domains.go
new file mode 100644
index 0000000..f3e4174
--- /dev/null
+++ b/mtypes/inboxready_domains.go
@@ -0,0 +1,17 @@
+package mtypes
+
+import (
+	"github.com/mailgun/mailgun-go/v5/internal/types/inboxready"
+)
+
+const (
+	InboxreadyDomainsVersion  = 1
+	InboxreadyDomainsEndpoint = "inboxready/domains"
+)
+
+type MonitoredDomain = inboxready.InboxReadyGithubComMailgunInboxreadyModelDomain
+
+type AddDomainToMonitoringOptions = inboxready.POSTV1InboxreadyDomainsParams
+type DeleteMonitoredDomainOptions inboxready.DELETEV1InboxreadyDomainsParams
+
+type AddDomainToMonitoringResponse = inboxready.InboxReadyGithubComMailgunInboxreadyAPIAddDomainResponse


Test patch:
diff --git a/alerts_test.go b/alerts_test.go
index a34e1d7..d2037f7 100644
--- a/alerts_test.go
+++ b/alerts_test.go
@@ -27,7 +27,7 @@ func TestAddAlert(t *testing.T) {
 	require.NoError(t, err)
 
 	req := mtypes.AlertsEventSettingRequest{
-		Channel:   mtypes.EmailChannel,
+		Channel:   mtypes.AlertsEmailChannel,
 		EventType: "ip_listed",
 		Settings: mtypes.AlertsChannelSettings{
 			Emails: []string{"mail1@example.com", "mail2@example.com"},
diff --git a/inboxready_domains_test.go b/inboxready_domains_test.go
new file mode 100644
index 0000000..189cb78
--- /dev/null
+++ b/inboxready_domains_test.go
@@ -0,0 +1,25 @@
+package mailgun_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/mailgun/mailgun-go/v5"
+	"github.com/mailgun/mailgun-go/v5/mtypes"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAddDomainToMonitoring(t *testing.T) {
+	mg := mailgun.NewMailgun(testKey)
+	err := mg.SetAPIBase(server.URL())
+	require.NoError(t, err)
+
+	req := mtypes.AddDomainToMonitoringOptions{
+		Domain: testDomain,
+	}
+	got, err := mg.AddDomainToMonitoring(context.Background(), req)
+	require.NoError(t, err)
+
+	assert.Equal(t, testDomain, got.Domain.Name)
+}