Repository: mailgun/mailgun-go
Pull request #414 (2025-04-13 16:11:02)

DE-1527 Support Alerts endpoints


Patch:
diff --git a/alerts.go b/alerts.go
new file mode 100644
index 0000000..c9014e9
--- /dev/null
+++ b/alerts.go
@@ -0,0 +1,69 @@
+package mailgun
+
+// https://documentation.mailgun.com/docs/inboxready/openapi-final/tag/Alerts/
+
+import (
+	"context"
+
+	"github.com/google/uuid"
+	"github.com/mailgun/mailgun-go/v5/mtypes"
+)
+
+type ListAlertsEventsOptions struct{}
+
+// ListAlertsEvents list of events that you can choose to receive alerts for.
+func (mg *Client) ListAlertsEvents(ctx context.Context, _ *ListAlertsEventsOptions,
+) (*mtypes.AlertsEventsResponse, error) {
+	r := newHTTPRequest(generateApiUrl(mg, mtypes.AlertsVersion, mtypes.AlertsEndpoint))
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+	r.setClient(mg.HTTPClient())
+
+	var resp mtypes.AlertsEventsResponse
+	if err := getResponseFromJSON(ctx, r, &resp); err != nil {
+		return nil, err
+	}
+
+	return &resp, nil
+}
+
+type ListAlertsOptions struct{}
+
+// ListAlerts returns a list of all configured alert settings for your account.
+func (mg *Client) ListAlerts(ctx context.Context, _ *ListAlertsOptions,
+) (*mtypes.AlertsSettingsResponse, error) {
+	r := newHTTPRequest(generateApiUrl(mg, mtypes.AlertsVersion, mtypes.AlertsSettingsEndpoint))
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+	r.setClient(mg.HTTPClient())
+
+	var resp mtypes.AlertsSettingsResponse
+	if err := getResponseFromJSON(ctx, r, &resp); err != nil {
+		return nil, err
+	}
+
+	return &resp, nil
+}
+
+func (mg *Client) AddAlert(ctx context.Context, req mtypes.AlertsEventSettingRequest,
+) (*mtypes.AlertsEventSettingResponse, error) {
+	r := newHTTPRequest(generateApiUrl(mg, mtypes.AlertsVersion, mtypes.AlertsSettingsEndpoint))
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+	r.setClient(mg.HTTPClient())
+
+	payload := newJSONEncodedPayload(req)
+	var resp mtypes.AlertsEventSettingResponse
+	if err := postResponseFromJSON(ctx, r, payload, &resp); err != nil {
+		return nil, err
+	}
+
+	return &resp, nil
+}
+
+func (mg *Client) DeleteAlert(ctx context.Context, id uuid.UUID) error {
+	r := newHTTPRequest(generateApiUrl(mg, mtypes.AlertsVersion, mtypes.AlertsSettingsEndpoint+"/"+id.String()))
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+	r.setClient(mg.HTTPClient())
+
+	_, err := makeDeleteRequest(ctx, r)
+
+	return err
+}
diff --git a/go.mod b/go.mod
index 13f1195..b19418f 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.22
 
 require (
 	github.com/go-chi/chi/v5 v5.2.1
+	github.com/google/uuid v1.6.0
 	github.com/json-iterator/go v1.1.12
 	github.com/mailgun/errors v0.4.0
 	github.com/oapi-codegen/runtime v1.1.1
@@ -12,7 +13,6 @@ require (
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/google/uuid v1.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 0a6bf75..f685e77 100644
--- a/go.sum
+++ b/go.sum
@@ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
 github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
-github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
diff --git a/mailgun.go b/mailgun.go
index 703ec05..daba0db 100644
--- a/mailgun.go
+++ b/mailgun.go
@@ -83,6 +83,7 @@ import (
 	"strconv"
 	"time"
 
+	"github.com/google/uuid"
 	"github.com/mailgun/mailgun-go/v5/mtypes"
 )
 
@@ -244,6 +245,11 @@ type Mailgun interface {
 
 	ValidateEmail(ctx context.Context, email string, mailBoxVerify bool) (mtypes.ValidateEmailResponse, error)
 
+	ListAlertsEvents(context.Context, *ListAlertsEventsOptions) (*mtypes.AlertsEventsResponse, error)
+	ListAlerts(context.Context, *ListAlertsOptions) (*mtypes.AlertsSettingsResponse, error)
+	AddAlert(context.Context, mtypes.AlertsEventSettingRequest) (*mtypes.AlertsEventSettingResponse, error)
+	DeleteAlert(ctx context.Context, id uuid.UUID) 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 62a499a..6d7bbdf 100644
--- a/mocks/mock.go
+++ b/mocks/mock.go
@@ -125,9 +125,13 @@ func NewServer() *Server {
 	r.Route("/v5", func(r chi.Router) {
 		ms.addSubaccountRoutes(r)
 	})
+
+	// self defined API version routes
+
 	ms.addDomainRoutes(r) // mix of v3 and v4
 	ms.addValidationRoutes(r)
 	ms.addAnalyticsRoutes(r)
+	ms.addAlertsRoutes(r)
 
 	// Start the server
 	ms.srv = httptest.NewServer(r)
@@ -143,6 +147,19 @@ func (ms *Server) URL() string {
 	return ms.srv.URL
 }
 
+func (ms *Server) ok(w http.ResponseWriter, _ *http.Request) {
+	resp := okResp{
+		Message: "OK",
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	toJSON(w, resp)
+}
+
+func (ms *Server) noContent(w http.ResponseWriter, _ *http.Request) {
+	w.WriteHeader(http.StatusNoContent)
+}
+
 func toJSON(w http.ResponseWriter, obj any) {
 	if err := json.NewEncoder(w).Encode(obj); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/mocks/mock_alerts.go b/mocks/mock_alerts.go
new file mode 100644
index 0000000..772c4de
--- /dev/null
+++ b/mocks/mock_alerts.go
@@ -0,0 +1,62 @@
+package mocks
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"github.com/go-chi/chi/v5"
+	"github.com/google/uuid"
+	"github.com/mailgun/mailgun-go/v5/internal/types/inboxready"
+	"github.com/mailgun/mailgun-go/v5/mtypes"
+)
+
+var alertID = uuid.MustParse("12345678-1234-5678-1234-123456789012")
+
+const uuidRE = `{id:[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}}`
+
+func (ms *Server) addAlertsRoutes(r chi.Router) {
+	r.Get("/v1/"+mtypes.AlertsSettingsEndpoint, ms.listAlerts)
+	r.Post("/v1/"+mtypes.AlertsSettingsEndpoint, ms.addAlert)
+	r.Delete("/v1/"+mtypes.AlertsSettingsEndpoint+"/"+uuidRE, ms.ok)
+}
+
+func (ms *Server) listAlerts(w http.ResponseWriter, _ *http.Request) {
+	resp := mtypes.AlertsSettingsResponse{
+		Events: []mtypes.AlertsEventSettingResponse{
+			{
+				Channel:   mtypes.EmailChannel,
+				EventType: "ip_listed",
+				ID:        ptr(uuid.New()),
+				Settings: mtypes.AlertsChannelSettings{
+					Emails: []string{"mail1@example.com", "mail2@example.com"},
+				},
+			},
+			{
+				Channel:   mtypes.WebhookChannel,
+				EventType: "ip_delisted",
+				ID:        ptr(uuid.New()),
+				Settings: mtypes.AlertsChannelSettings{
+					URL: ptr("https://example.com/hook"),
+				},
+			},
+		},
+		Webhooks: inboxready.GithubComMailgunAlertsInternalSettingsWebhooks{
+			SigningKey: "secret-key",
+		},
+	}
+
+	toJSON(w, resp)
+}
+
+func (ms *Server) addAlert(w http.ResponseWriter, r *http.Request) {
+	var v mtypes.AlertsEventSettingResponse
+
+	err := json.NewDecoder(r.Body).Decode(&v)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	v.ID = ptr(alertID)
+	toJSON(w, v)
+}
diff --git a/mocks/mock_analytics.go b/mocks/mock_analytics.go
index 8bcce2b..54627ca 100644
--- a/mocks/mock_analytics.go
+++ b/mocks/mock_analytics.go
@@ -7,6 +7,7 @@ import (
 	"github.com/mailgun/mailgun-go/v5/mtypes"
 )
 
+// TODO(vtopc): move to mtypes?
 const metricsEndpoint = "analytics/metrics"
 
 func (ms *Server) addAnalyticsRoutes(r chi.Router) {
diff --git a/mtypes/alerts.go b/mtypes/alerts.go
new file mode 100644
index 0000000..34245d3
--- /dev/null
+++ b/mtypes/alerts.go
@@ -0,0 +1,25 @@
+package mtypes
+
+import (
+	"github.com/mailgun/mailgun-go/v5/internal/types/inboxready"
+)
+
+const (
+	AlertsEndpoint         = "alerts"
+	AlertsSettingsEndpoint = AlertsEndpoint + "/settings"
+	AlertsVersion          = 1
+)
+
+// channels
+const (
+	EmailChannel   = "email"
+	WebhookChannel = "webhook"
+	SlackChannel   = "slack"
+)
+
+type AlertsEventsResponse = inboxready.GithubComMailgunAlertsInternalAPIListAlertsEventsResp
+
+type AlertsEventSettingRequest = inboxready.POSTV1AlertsSettingsEventsJSONRequestBody
+type AlertsSettingsResponse = inboxready.GithubComMailgunAlertsInternalAPISettingsResponse
+type AlertsEventSettingResponse = inboxready.GithubComMailgunAlertsInternalSettingsEventSettings
+type AlertsChannelSettings = inboxready.GithubComMailgunAlertsInternalSettingsChannelSettings


Test patch:
diff --git a/alerts_test.go b/alerts_test.go
new file mode 100644
index 0000000..a34e1d7
--- /dev/null
+++ b/alerts_test.go
@@ -0,0 +1,57 @@
+package mailgun_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/uuid"
+	"github.com/mailgun/mailgun-go/v5"
+	"github.com/mailgun/mailgun-go/v5/mtypes"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestListAlerts(t *testing.T) {
+	mg := mailgun.NewMailgun(testKey)
+	err := mg.SetAPIBase(server.URL())
+	require.NoError(t, err)
+
+	resp, err := mg.ListAlerts(context.Background(), nil)
+	require.NoError(t, err)
+	assert.Len(t, resp.Events, 2)
+}
+
+func TestAddAlert(t *testing.T) {
+	mg := mailgun.NewMailgun(testKey)
+	err := mg.SetAPIBase(server.URL())
+	require.NoError(t, err)
+
+	req := mtypes.AlertsEventSettingRequest{
+		Channel:   mtypes.EmailChannel,
+		EventType: "ip_listed",
+		Settings: mtypes.AlertsChannelSettings{
+			Emails: []string{"mail1@example.com", "mail2@example.com"},
+		},
+	}
+
+	wantResp := mtypes.AlertsEventSettingResponse{
+		Channel:    req.Channel,
+		DisabledAt: nil,
+		EventType:  req.EventType,
+		ID:         ptr(uuid.MustParse("12345678-1234-5678-1234-123456789012")),
+		Settings:   req.Settings,
+	}
+
+	resp, err := mg.AddAlert(context.Background(), req)
+	require.NoError(t, err)
+	assert.Equal(t, wantResp, *resp)
+}
+
+func TestDeleteAlert(t *testing.T) {
+	mg := mailgun.NewMailgun(testKey)
+	err := mg.SetAPIBase(server.URL())
+	require.NoError(t, err)
+
+	err = mg.DeleteAlert(context.Background(), uuid.New())
+	require.NoError(t, err)
+}