From 37ced1dc1e891ca83b9301bd4093929d65d9289a Mon Sep 17 00:00:00 2001 From: Derich Pacheco Date: Wed, 14 Aug 2024 16:29:20 -0300 Subject: [PATCH] feat: Schedule, cancel and update email (#38) Co-authored-by: Felipe Volpone --- emails.go | 89 ++++++++++++++++++++++++++++++++++++-- emails_test.go | 54 +++++++++++++++++++++++ errors.go | 5 ++- examples/schedule_email.go | 49 +++++++++++++++++++++ resend.go | 2 +- 5 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 examples/schedule_email.go diff --git a/emails.go b/emails.go index 3372954..fc11849 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 { @@ -21,14 +21,33 @@ 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"` } -// SendEmailResponse is the response from the SendEmail 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 Send call. type SendEmailResponse struct { Id string `json:"id"` } -// Email provides the structure for the response from the GetEmail 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 Update call. +type UpdateEmailResponse struct { + Id string `json:"id"` + Object string `json:"object"` +} + +// Email provides the structure for the response from the Get call. type Email struct { Id string `json:"id"` Object string `json:"object"` @@ -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 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 + + // 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..7f7c141 --- /dev/null +++ b/examples/schedule_email.go @@ -0,0 +1,49 @@ +package examples + +import ( + "context" + "fmt" + "os" + + "github.com/resend/resend-go/v2" +) + +func scheduleEmail() { + 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 + updatedEmail, err := client.Emails.UpdateWithContext(ctx, updateParams) + if err != nil { + panic(err) + } + fmt.Printf("%v\n", updatedEmail) + + canceled, err := client.Emails.CancelWithContext(ctx, "32723fee-8502-4b58-8b5e-bfd98f453ced") + if err != nil { + panic(err) + } + fmt.Printf("%v\n", canceled) +} 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" )