diff --git a/Readme.md b/Readme.md index dde543a..bbac8d4 100644 --- a/Readme.md +++ b/Readme.md @@ -125,6 +125,16 @@ MailerSend Golang SDK - [Invite a user](#invite-a-user) - [Update a user](#update-a-user) - [Delete a user](#delete-a-user) + - [DMARC Monitoring](#dmarc-monitoring) + - [Get a list of DMARC monitors](#get-a-list-of-dmarc-monitors) + - [Create a DMARC monitor](#create-a-dmarc-monitor) + - [Update a DMARC monitor](#update-a-dmarc-monitor) + - [Delete a DMARC monitor](#delete-a-dmarc-monitor) + - [Get aggregated DMARC report](#get-aggregated-dmarc-report) + - [Get IP-specific DMARC report](#get-ip-specific-dmarc-report) + - [Get DMARC report sources](#get-dmarc-report-sources) + - [Mark an IP as favorite](#mark-an-ip-as-favorite) + - [Remove an IP from favorites](#remove-an-ip-from-favorites) - [Other Endpoints](#other-endpoints) - [Get an API Quota](#get-an-api-quota) - [Types](#types) @@ -3628,6 +3638,286 @@ func main() { } ``` +## DMARC Monitoring + +### Get a list of DMARC monitors + +```go +package main + +import ( + "context" + "os" + "log" + "time" + + "github.com/mailersend/mailersend-go" +) + +func main() { + ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY")) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + options := &mailersend.ListDmarcMonitorOptions{ + Page: 1, + Limit: 25, + } + + _, _, err := ms.DmarcMonitoring.List(ctx, options) + if err != nil { + log.Fatal(err) + } +} +``` + +### Create a DMARC monitor + +```go +package main + +import ( + "context" + "os" + "log" + "time" + + "github.com/mailersend/mailersend-go" +) + +func main() { + ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY")) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + options := &mailersend.CreateDmarcMonitorOptions{ + DomainID: "domain-id", + } + + _, _, err := ms.DmarcMonitoring.Create(ctx, options) + if err != nil { + log.Fatal(err) + } +} +``` + +### Update a DMARC monitor + +```go +package main + +import ( + "context" + "os" + "log" + "time" + + "github.com/mailersend/mailersend-go" +) + +func main() { + ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY")) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + options := &mailersend.UpdateDmarcMonitorOptions{ + MonitorID: "monitor-id", + WantedDmarcRecord: "v=DMARC1; p=reject;", + } + + _, _, err := ms.DmarcMonitoring.Update(ctx, options) + if err != nil { + log.Fatal(err) + } +} +``` + +### Delete a DMARC monitor + +```go +package main + +import ( + "context" + "os" + "log" + "time" + + "github.com/mailersend/mailersend-go" +) + +func main() { + ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY")) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + _, err := ms.DmarcMonitoring.Delete(ctx, "monitor-id") + if err != nil { + log.Fatal(err) + } +} +``` + +### Get aggregated DMARC report + +```go +package main + +import ( + "context" + "os" + "log" + "time" + + "github.com/mailersend/mailersend-go" +) + +func main() { + ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY")) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + options := &mailersend.ListDmarcReportOptions{ + MonitorID: "monitor-id", + Page: 1, + Limit: 25, + } + + _, _, err := ms.DmarcMonitoring.GetAggregatedReport(ctx, options) + if err != nil { + log.Fatal(err) + } +} +``` + +### Get IP-specific DMARC report + +```go +package main + +import ( + "context" + "os" + "log" + "time" + + "github.com/mailersend/mailersend-go" +) + +func main() { + ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY")) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + _, _, err := ms.DmarcMonitoring.GetIPReport(ctx, "monitor-id", "1.2.3.4") + if err != nil { + log.Fatal(err) + } +} +``` + +### Get DMARC report sources + +```go +package main + +import ( + "context" + "os" + "log" + "time" + + "github.com/mailersend/mailersend-go" +) + +func main() { + ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY")) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + options := &mailersend.ListDmarcReportSourcesOptions{ + MonitorID: "monitor-id", + Page: 1, + Limit: 25, + } + + _, _, err := ms.DmarcMonitoring.GetReportSources(ctx, options) + if err != nil { + log.Fatal(err) + } +} +``` + +### Mark an IP as favorite + +```go +package main + +import ( + "context" + "os" + "log" + "time" + + "github.com/mailersend/mailersend-go" +) + +func main() { + ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY")) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + _, err := ms.DmarcMonitoring.MarkIPFavorite(ctx, "monitor-id", "1.2.3.4") + if err != nil { + log.Fatal(err) + } +} +``` + +### Remove an IP from favorites + +```go +package main + +import ( + "context" + "os" + "log" + "time" + + "github.com/mailersend/mailersend-go" +) + +func main() { + ms := mailersend.NewMailersend(os.Getenv("MAILERSEND_API_KEY")) + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + _, err := ms.DmarcMonitoring.RemoveIPFavorite(ctx, "monitor-id", "1.2.3.4") + if err != nil { + log.Fatal(err) + } +} +``` + ## Other Endpoints ### Get an API Quota diff --git a/dmarc_monitoring.go b/dmarc_monitoring.go new file mode 100644 index 0000000..eef92fa --- /dev/null +++ b/dmarc_monitoring.go @@ -0,0 +1,267 @@ +package mailersend + +import ( + "context" + "fmt" + "net/http" +) + +const dmarcMonitoringBasePath = "/dmarc-monitoring" + +// DmarcMonitoringService defines the interface for DMARC Monitoring API operations. +type DmarcMonitoringService interface { + List(ctx context.Context, options *ListDmarcMonitorOptions) (*DmarcMonitorRoot, *Response, error) + Create(ctx context.Context, options *CreateDmarcMonitorOptions) (*SingleDmarcMonitorRoot, *Response, error) + Update(ctx context.Context, options *UpdateDmarcMonitorOptions) (*SingleDmarcMonitorRoot, *Response, error) + Delete(ctx context.Context, monitorID string) (*Response, error) + GetAggregatedReport(ctx context.Context, options *ListDmarcReportOptions) (*DmarcAggregatedReportRoot, *Response, error) + GetIPReport(ctx context.Context, monitorID string, ip string) (*DmarcIPReportRoot, *Response, error) + GetReportSources(ctx context.Context, options *ListDmarcReportSourcesOptions) (*DmarcReportSourcesRoot, *Response, error) + MarkIPFavorite(ctx context.Context, monitorID string, ip string) (*Response, error) + RemoveIPFavorite(ctx context.Context, monitorID string, ip string) (*Response, error) +} + +type dmarcMonitoringService struct { + *service +} + +// DmarcMonitorRoot - list of DMARC monitors response +type DmarcMonitorRoot struct { + Data []DmarcMonitor `json:"data"` + Links Links `json:"links"` + Meta Meta `json:"meta"` +} + +// SingleDmarcMonitorRoot - single DMARC monitor response +type SingleDmarcMonitorRoot struct { + Data DmarcMonitor `json:"data"` +} + +// DmarcMonitor - a single DMARC monitor +type DmarcMonitor struct { + ID string `json:"id"` + DomainID string `json:"domain_id"` + Domain Domain `json:"domain"` + DmarcRecord string `json:"dmarc_record"` + WantedDmarcRecord string `json:"wanted_dmarc_record"` + IsDmarcVerified bool `json:"is_dmarc_verified"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +// DmarcAggregatedReportRoot - aggregated DMARC report response +type DmarcAggregatedReportRoot struct { + Data []DmarcAggregatedReport `json:"data"` + Links Links `json:"links"` + Meta Meta `json:"meta"` +} + +// DmarcAggregatedReport - a single aggregated report entry +type DmarcAggregatedReport struct { + IP string `json:"ip"` + Count int `json:"count"` + DmarcPassCount int `json:"dmarc_pass_count"` + DmarcFailCount int `json:"dmarc_fail_count"` + SpfPassCount int `json:"spf_pass_count"` + SpfFailCount int `json:"spf_fail_count"` + DkimPassCount int `json:"dkim_pass_count"` + DkimFailCount int `json:"dkim_fail_count"` + Disposition string `json:"disposition"` + Country string `json:"country"` + IsFavorite bool `json:"is_favorite"` + CreatedAt string `json:"created_at"` +} + +// DmarcIPReportRoot - IP-specific DMARC report response +type DmarcIPReportRoot struct { + Data []DmarcIPReport `json:"data"` + Links Links `json:"links"` + Meta Meta `json:"meta"` +} + +// DmarcIPReport - a single IP report entry +type DmarcIPReport struct { + IP string `json:"ip"` + Count int `json:"count"` + Disposition string `json:"disposition"` + DkimResult string `json:"dkim_result"` + SpfResult string `json:"spf_result"` + CreatedAt string `json:"created_at"` +} + +// DmarcReportSourcesRoot - DMARC report sources response +type DmarcReportSourcesRoot struct { + Data []DmarcReportSource `json:"data"` + Links Links `json:"links"` + Meta Meta `json:"meta"` +} + +// DmarcReportSource - a single report source +type DmarcReportSource struct { + ID string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + CreatedAt string `json:"created_at"` +} + +// ListDmarcMonitorOptions - modifies the behavior of dmarcMonitoringService.List +type ListDmarcMonitorOptions struct { + Page int `url:"page,omitempty"` + Limit int `url:"limit,omitempty"` +} + +// CreateDmarcMonitorOptions - modifies the behavior of dmarcMonitoringService.Create +type CreateDmarcMonitorOptions struct { + DomainID string `json:"domain_id"` +} + +// UpdateDmarcMonitorOptions - modifies the behavior of dmarcMonitoringService.Update +type UpdateDmarcMonitorOptions struct { + MonitorID string `json:"-"` + WantedDmarcRecord string `json:"wanted_dmarc_record"` +} + +// ListDmarcReportOptions - modifies the behavior of dmarcMonitoringService.GetAggregatedReport +type ListDmarcReportOptions struct { + MonitorID string `url:"-"` + Page int `url:"page,omitempty"` + Limit int `url:"limit,omitempty"` +} + +// ListDmarcReportSourcesOptions - modifies the behavior of dmarcMonitoringService.GetReportSources +type ListDmarcReportSourcesOptions struct { + MonitorID string `url:"-"` + Page int `url:"page,omitempty"` + Limit int `url:"limit,omitempty"` +} + +func (s *dmarcMonitoringService) List(ctx context.Context, options *ListDmarcMonitorOptions) (*DmarcMonitorRoot, *Response, error) { + req, err := s.client.newRequest(http.MethodGet, dmarcMonitoringBasePath, options) + if err != nil { + return nil, nil, err + } + + root := new(DmarcMonitorRoot) + res, err := s.client.do(ctx, req, root) + if err != nil { + return nil, res, err + } + + return root, res, nil +} + +func (s *dmarcMonitoringService) Create(ctx context.Context, options *CreateDmarcMonitorOptions) (*SingleDmarcMonitorRoot, *Response, error) { + req, err := s.client.newRequest(http.MethodPost, dmarcMonitoringBasePath, options) + if err != nil { + return nil, nil, err + } + + root := new(SingleDmarcMonitorRoot) + res, err := s.client.do(ctx, req, root) + if err != nil { + return nil, res, err + } + + return root, res, nil +} + +func (s *dmarcMonitoringService) Update(ctx context.Context, options *UpdateDmarcMonitorOptions) (*SingleDmarcMonitorRoot, *Response, error) { + path := fmt.Sprintf("%s/%s", dmarcMonitoringBasePath, options.MonitorID) + + req, err := s.client.newRequest(http.MethodPut, path, options) + if err != nil { + return nil, nil, err + } + + root := new(SingleDmarcMonitorRoot) + res, err := s.client.do(ctx, req, root) + if err != nil { + return nil, res, err + } + + return root, res, nil +} + +func (s *dmarcMonitoringService) Delete(ctx context.Context, monitorID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", dmarcMonitoringBasePath, monitorID) + + req, err := s.client.newRequest(http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + return s.client.do(ctx, req, nil) +} + +func (s *dmarcMonitoringService) GetAggregatedReport(ctx context.Context, options *ListDmarcReportOptions) (*DmarcAggregatedReportRoot, *Response, error) { + path := fmt.Sprintf("%s/%s/report", dmarcMonitoringBasePath, options.MonitorID) + + req, err := s.client.newRequest(http.MethodGet, path, options) + if err != nil { + return nil, nil, err + } + + root := new(DmarcAggregatedReportRoot) + res, err := s.client.do(ctx, req, root) + if err != nil { + return nil, res, err + } + + return root, res, nil +} + +func (s *dmarcMonitoringService) GetIPReport(ctx context.Context, monitorID string, ip string) (*DmarcIPReportRoot, *Response, error) { + path := fmt.Sprintf("%s/%s/report/%s", dmarcMonitoringBasePath, monitorID, ip) + + req, err := s.client.newRequest(http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(DmarcIPReportRoot) + res, err := s.client.do(ctx, req, root) + if err != nil { + return nil, res, err + } + + return root, res, nil +} + +func (s *dmarcMonitoringService) GetReportSources(ctx context.Context, options *ListDmarcReportSourcesOptions) (*DmarcReportSourcesRoot, *Response, error) { + path := fmt.Sprintf("%s/%s/report-sources", dmarcMonitoringBasePath, options.MonitorID) + + req, err := s.client.newRequest(http.MethodGet, path, options) + if err != nil { + return nil, nil, err + } + + root := new(DmarcReportSourcesRoot) + res, err := s.client.do(ctx, req, root) + if err != nil { + return nil, res, err + } + + return root, res, nil +} + +func (s *dmarcMonitoringService) MarkIPFavorite(ctx context.Context, monitorID string, ip string) (*Response, error) { + path := fmt.Sprintf("%s/%s/favorite/%s", dmarcMonitoringBasePath, monitorID, ip) + + req, err := s.client.newRequest(http.MethodPut, path, nil) + if err != nil { + return nil, err + } + + return s.client.do(ctx, req, nil) +} + +func (s *dmarcMonitoringService) RemoveIPFavorite(ctx context.Context, monitorID string, ip string) (*Response, error) { + path := fmt.Sprintf("%s/%s/favorite/%s", dmarcMonitoringBasePath, monitorID, ip) + + req, err := s.client.newRequest(http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + return s.client.do(ctx, req, nil) +} diff --git a/dmarc_monitoring_test.go b/dmarc_monitoring_test.go new file mode 100644 index 0000000..615e8eb --- /dev/null +++ b/dmarc_monitoring_test.go @@ -0,0 +1,60 @@ +package mailersend_test + +import ( + "testing" + + "github.com/mailersend/mailersend-go" + "github.com/stretchr/testify/assert" +) + +func TestCanCreateDmarcMonitorListOptions(t *testing.T) { + options := mailersend.ListDmarcMonitorOptions{ + Page: 1, + Limit: 25, + } + + assert.Equal(t, 1, options.Page) + assert.Equal(t, 25, options.Limit) +} + +func TestCanCreateDmarcMonitorOptions(t *testing.T) { + options := mailersend.CreateDmarcMonitorOptions{ + DomainID: "domain-id", + } + + assert.Equal(t, "domain-id", options.DomainID) +} + +func TestCanCreateUpdateDmarcMonitorOptions(t *testing.T) { + options := mailersend.UpdateDmarcMonitorOptions{ + MonitorID: "monitor-id", + WantedDmarcRecord: "v=DMARC1; p=reject;", + } + + assert.Equal(t, "monitor-id", options.MonitorID) + assert.Equal(t, "v=DMARC1; p=reject;", options.WantedDmarcRecord) +} + +func TestCanCreateDmarcReportOptions(t *testing.T) { + options := mailersend.ListDmarcReportOptions{ + MonitorID: "monitor-id", + Page: 1, + Limit: 25, + } + + assert.Equal(t, "monitor-id", options.MonitorID) + assert.Equal(t, 1, options.Page) + assert.Equal(t, 25, options.Limit) +} + +func TestCanCreateDmarcReportSourcesOptions(t *testing.T) { + options := mailersend.ListDmarcReportSourcesOptions{ + MonitorID: "monitor-id", + Page: 1, + Limit: 25, + } + + assert.Equal(t, "monitor-id", options.MonitorID) + assert.Equal(t, 1, options.Page) + assert.Equal(t, 25, options.Limit) +} diff --git a/helpers.go b/helpers.go index c3c9fa0..37b2f35 100644 --- a/helpers.go +++ b/helpers.go @@ -6,6 +6,7 @@ const ( EventActivityDelivered = "activity.delivered" // Fired when your email is successfully delivered with no errors. EventActivitySoftBounced = "activity.soft_bounced" // Fired when your email is not delivered because it soft bounced. EventActivityHardBounced = "activity.hard_bounced" // Fired when your email is not delivered. + EventActivityDeferred = "activity.deferred" // Fired when your email is temporarily deferred by the receiving server. EventActivityOpened = "activity.opened" // Fired when the recipient receives your email and opens it. EventActivityOpenedUnique = "activity.opened_unique" // Fired when the recipient receives your email and opens it only for the first time. EventActivityClicked = "activity.clicked" // Fired when the recipient clicks a link in your email. @@ -31,3 +32,20 @@ const ( const ( EventInboundForwardFailed = "inbound_forward.failed" // Fired when an inbound message fails to forward. ) + +// Email verification-related event constants +const ( + EventEmailSingleVerified = "email_single.verified" // Fired when a single email address has been successfully verified. + EventEmailListVerified = "email_list.verified" // Fired when an email list has been successfully verified. +) + +// Bulk email-related event constants +const ( + EventBulkEmailCompleted = "bulk_email.completed" // Fired when a bulk email sending has been completed. +) + +// Recipient-related event constants +const ( + EventRecipientOnHoldAdded = "recipient.on_hold_added" // Fired when a recipient is added to the on-hold list. + EventRecipientOnHoldRemoved = "recipient.on_hold_removed" // Fired when a recipient is removed from the on-hold list. +) diff --git a/mailersend.go b/mailersend.go index 9ba3491..95e954a 100644 --- a/mailersend.go +++ b/mailersend.go @@ -49,6 +49,7 @@ type Mailersend struct { ApiQuota ApiQuotaService SmtpUser SmtpUserService User UserService + DmarcMonitoring DmarcMonitoringService } type service struct { @@ -136,6 +137,7 @@ func NewMailersend(apiKey string) *Mailersend { ms.ApiQuota = &apiQuotaService{&ms.common} ms.SmtpUser = &smtpUserService{&ms.common} ms.User = &userService{&ms.common} + ms.DmarcMonitoring = &dmarcMonitoringService{&ms.common} return ms }