From 37a7b6733d44beab6f0d9592ed523f5c60e5ead4 Mon Sep 17 00:00:00 2001 From: Derich Pacheco Date: Mon, 12 Aug 2024 23:24:20 -0300 Subject: [PATCH 1/4] feat: schedule, cancel and update email --- emails.go | 83 ++++++++++++++++++++++++++++++++++++++ emails_test.go | 54 +++++++++++++++++++++++++ errors.go | 5 ++- examples/schedule_email.go | 50 +++++++++++++++++++++++ resend.go | 2 +- 5 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 examples/schedule_email.go diff --git a/emails.go b/emails.go index 3372954..4760fae 100644 --- a/emails.go +++ b/emails.go @@ -21,6 +21,13 @@ type SendEmailRequest struct { Tags []Tag `json:"tags,omitempty"` Attachments []*Attachment `json:"attachments,omitempty"` Headers map[string]string `json:"headers,omitempty"` + ScheduledAt string `json:"scheduled_at,omitempty"` +} + +// CancelScheduledEmailResponse is the response from the CancelEmail call. +type CancelScheduledEmailResponse struct { + Id string `json:"id"` + Object string `json:"object"` } // SendEmailResponse is the response from the SendEmail call. @@ -28,6 +35,18 @@ type SendEmailResponse struct { Id string `json:"id"` } +// UpdateEmailRequest is the request object for the UpdateEmail call. +type UpdateEmailRequest struct { + Id string `json:"id"` + ScheduledAt string `json:"scheduled_at"` +} + +// UpdateEmailResponse is the type that represents the response from the UpdateEmail call. +type UpdateEmailResponse struct { + Id string `json:"id"` + Object string `json:"object"` +} + // Email provides the structure for the response from the GetEmail call. type Email struct { Id string `json:"id"` @@ -88,6 +107,10 @@ func (a *Attachment) MarshalJSON() ([]byte, error) { } type EmailsSvc interface { + CancelWithContext(ctx context.Context, emailID string) (*CancelScheduledEmailResponse, error) + Cancel(emailID string) (*CancelScheduledEmailResponse, error) + UpdateWithContext(ctx context.Context, params *UpdateEmailRequest) (*UpdateEmailResponse, error) + Update(params *UpdateEmailRequest) (*UpdateEmailResponse, error) SendWithContext(ctx context.Context, params *SendEmailRequest) (*SendEmailResponse, error) Send(params *SendEmailRequest) (*SendEmailResponse, error) GetWithContext(ctx context.Context, emailID string) (*Email, error) @@ -98,6 +121,66 @@ type EmailsSvcImpl struct { client *Client } +// Cancel cancels an email by ID +// https://resend.com/docs/api-reference/emails/cancel-email +func (s *EmailsSvcImpl) Cancel(emailID string) (*CancelScheduledEmailResponse, error) { + return s.CancelWithContext(context.Background(), emailID) +} + +// CancelWithContext cancels an email by ID +// https://resend.com/docs/api-reference/emails/cancel-email +func (s *EmailsSvcImpl) CancelWithContext(ctx context.Context, emailID string) (*CancelScheduledEmailResponse, error) { + path := "emails/" + emailID + "/cancel" + + // Prepare request + req, err := s.client.NewRequest(ctx, http.MethodPost, path, nil) + if err != nil { + return nil, ErrFailedToCreateEmailsSendRequest + } + + // Build response recipient obj + resp := new(CancelScheduledEmailResponse) + + // Send Request + _, err = s.client.Perform(req, resp) + + if err != nil { + return nil, err + } + + return resp, nil +} + +// Update updates an email with the given params +// https://resend.com/docs/api-reference/emails/update-email +func (s *EmailsSvcImpl) Update(params *UpdateEmailRequest) (*UpdateEmailResponse, error) { + return s.UpdateWithContext(context.Background(), params) +} + +// UpdateWithContext sends an email with the given params +// https://resend.com/docs/api-reference/emails/update-email +func (s *EmailsSvcImpl) UpdateWithContext(ctx context.Context, params *UpdateEmailRequest) (*UpdateEmailResponse, error) { + path := "emails/" + params.Id + + // Prepare request + req, err := s.client.NewRequest(ctx, http.MethodPatch, path, params) + if err != nil { + return nil, ErrFailedToCreateUpdateEmailRequest + } + + // Build response recipient obj + updateEmailResponse := new(UpdateEmailResponse) + + // Send Request + _, err = s.client.Perform(req, updateEmailResponse) + + if err != nil { + return nil, err + } + + return updateEmailResponse, nil +} + // SendWithContext sends an email with the given params // https://resend.com/docs/api-reference/emails/send-email func (s *EmailsSvcImpl) SendWithContext(ctx context.Context, params *SendEmailRequest) (*SendEmailResponse, error) { diff --git a/emails_test.go b/emails_test.go index f238668..b6e5605 100644 --- a/emails_test.go +++ b/emails_test.go @@ -31,6 +31,35 @@ func teardown() { server.Close() } +func TestScheduleEmail(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/emails", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + ret := &SendEmailResponse{ + Id: "1923781293", + } + err := json.NewEncoder(w).Encode(&ret) + if err != nil { + panic(err) + } + }) + + req := &SendEmailRequest{ + To: []string{"d@e.com"}, + ScheduledAt: "2024-09-05T11:52:01.858Z", + } + resp, err := client.Emails.Send(req) + if err != nil { + t.Errorf("Emails.Send returned error: %v", err) + } + assert.Equal(t, resp.Id, "1923781293") +} + func TestSendEmail(t *testing.T) { setup() defer teardown() @@ -132,6 +161,31 @@ func TestGetEmail(t *testing.T) { assert.Equal(t, resp.Subject, "Hello World") } +func TestCancelScheduledEmail(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/emails/dacf4072-4119-4d88-932f-6202748ac7c8/cancel", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + ret := ` + { + "id": "dacf4072-4119-4d88-932f-6202748ac7c8", + "object": "email" + }` + fmt.Fprintf(w, ret) + }) + + resp, err := client.Emails.Cancel("dacf4072-4119-4d88-932f-6202748ac7c8") + if err != nil { + t.Errorf("Emails.Cancel returned error: %v", err) + } + assert.Equal(t, resp.Id, "dacf4072-4119-4d88-932f-6202748ac7c8") + assert.Equal(t, resp.Object, "email") +} + func testMethod(t *testing.T, r *http.Request, expected string) { if expected != r.Method { t.Errorf("Request method = %v, expected %v", r.Method, expected) diff --git a/errors.go b/errors.go index d1823c6..a363f78 100644 --- a/errors.go +++ b/errors.go @@ -11,6 +11,7 @@ var ( // EmailsSvc errors var ( - ErrFailedToCreateEmailsSendRequest = errors.New("[ERROR]: Failed to create SendEmail request") - ErrFailedToCreateEmailsGetRequest = errors.New("[ERROR]: Failed to create GetEmail request") + ErrFailedToCreateUpdateEmailRequest = errors.New("[ERROR]: Failed to create UpdateEmail request") + ErrFailedToCreateEmailsSendRequest = errors.New("[ERROR]: Failed to create SendEmail request") + ErrFailedToCreateEmailsGetRequest = errors.New("[ERROR]: Failed to create GetEmail request") ) diff --git a/examples/schedule_email.go b/examples/schedule_email.go new file mode 100644 index 0000000..ce778dd --- /dev/null +++ b/examples/schedule_email.go @@ -0,0 +1,50 @@ +package examples + +import ( + "context" + "fmt" + "os" + + "github.com/resend/resend-go/v2" +) + +func scheduledEmailExample() { + ctx := context.TODO() + apiKey := os.Getenv("RESEND_API_KEY") + + client := resend.NewClient(apiKey) + + // Schedule the email + params := &resend.SendEmailRequest{ + To: []string{"delivered@resend.dev"}, + From: "onboarding@resend.dev", + Text: "hello world", + Subject: "Hello from Golang", + ScheduledAt: "2024-09-05T11:52:01.858Z", + } + + sent, err := client.Emails.SendWithContext(ctx, params) + if err != nil { + panic(err) + } + fmt.Println(sent.Id) + + updateParams := &resend.UpdateEmailRequest{ + Id: sent.Id, + ScheduledAt: "2024-11-05T11:52:01.858Z", + } + + // Update the scheduled email + email, err := client.Emails.UpdateWithContext(ctx, updateParams) + if err != nil { + panic(err) + + } + fmt.Printf("%v\n", email) + + cancelled, err := client.Emails.CancelWithContext(ctx, sent.Id) + if err != nil { + panic(err) + } + fmt.Printf("%v\n", cancelled) +} diff --git a/resend.go b/resend.go index 0ae999d..01f21cf 100644 --- a/resend.go +++ b/resend.go @@ -12,7 +12,7 @@ import ( ) const ( - version = "2.10.0" + version = "2.11.0" userAgent = "resend-go/" + version contentType = "application/json" ) From ea104a87d3595a142bf3a7c2758b4844b276976f Mon Sep 17 00:00:00 2001 From: Derich Pacheco Date: Tue, 13 Aug 2024 11:03:28 -0300 Subject: [PATCH 2/4] example: small tweaks --- examples/schedule_email.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/schedule_email.go b/examples/schedule_email.go index ce778dd..7f7c141 100644 --- a/examples/schedule_email.go +++ b/examples/schedule_email.go @@ -8,7 +8,7 @@ import ( "github.com/resend/resend-go/v2" ) -func scheduledEmailExample() { +func scheduleEmail() { ctx := context.TODO() apiKey := os.Getenv("RESEND_API_KEY") @@ -35,16 +35,15 @@ func scheduledEmailExample() { } // Update the scheduled email - email, err := client.Emails.UpdateWithContext(ctx, updateParams) + updatedEmail, err := client.Emails.UpdateWithContext(ctx, updateParams) if err != nil { panic(err) - } - fmt.Printf("%v\n", email) + fmt.Printf("%v\n", updatedEmail) - cancelled, err := client.Emails.CancelWithContext(ctx, sent.Id) + canceled, err := client.Emails.CancelWithContext(ctx, "32723fee-8502-4b58-8b5e-bfd98f453ced") if err != nil { panic(err) } - fmt.Printf("%v\n", cancelled) + fmt.Printf("%v\n", canceled) } From 909dfd95eb5cf391b0e4afbb0f2999efceddab83 Mon Sep 17 00:00:00 2001 From: Derich Pacheco Date: Tue, 13 Aug 2024 11:05:34 -0300 Subject: [PATCH 3/4] doc: tweaks --- emails.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/emails.go b/emails.go index 4760fae..6bff81e 100644 --- a/emails.go +++ b/emails.go @@ -6,7 +6,7 @@ import ( "net/http" ) -// SendEmailRequest is the request object for the SendEmail call. +// SendEmailRequest is the request object for the Send call. // // See also https://resend.com/docs/api-reference/emails/send-email type SendEmailRequest struct { @@ -24,30 +24,30 @@ type SendEmailRequest struct { ScheduledAt string `json:"scheduled_at,omitempty"` } -// CancelScheduledEmailResponse is the response from the CancelEmail call. +// CancelScheduledEmailResponse is the response from the Cancel call. type CancelScheduledEmailResponse struct { Id string `json:"id"` Object string `json:"object"` } -// SendEmailResponse is the response from the SendEmail call. +// SendEmailResponse is the response from the Send call. type SendEmailResponse struct { Id string `json:"id"` } -// UpdateEmailRequest is the request object for the UpdateEmail call. +// UpdateEmailRequest is the request object for the Update call. type UpdateEmailRequest struct { Id string `json:"id"` ScheduledAt string `json:"scheduled_at"` } -// UpdateEmailResponse is the type that represents the response from the UpdateEmail call. +// UpdateEmailResponse is the type that represents the response from the Update call. type UpdateEmailResponse struct { Id string `json:"id"` Object string `json:"object"` } -// Email provides the structure for the response from the GetEmail call. +// Email provides the structure for the response from the Get call. type Email struct { Id string `json:"id"` Object string `json:"object"` From 93b59126e5381525c55e056a9f51e57ffbfb8da7 Mon Sep 17 00:00:00 2001 From: Derich Pacheco Date: Wed, 14 Aug 2024 15:31:55 -0300 Subject: [PATCH 4/4] Update emails.go Co-authored-by: Felipe Volpone --- emails.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emails.go b/emails.go index 6bff81e..fc11849 100644 --- a/emails.go +++ b/emails.go @@ -157,7 +157,7 @@ func (s *EmailsSvcImpl) Update(params *UpdateEmailRequest) (*UpdateEmailResponse return s.UpdateWithContext(context.Background(), params) } -// UpdateWithContext sends an email with the given params +// UpdateWithContext updates an email with the given params // https://resend.com/docs/api-reference/emails/update-email func (s *EmailsSvcImpl) UpdateWithContext(ctx context.Context, params *UpdateEmailRequest) (*UpdateEmailResponse, error) { path := "emails/" + params.Id