From 9525d01c1263f70dfaa4866175c712598040dd8f Mon Sep 17 00:00:00 2001 From: Clement James Date: Fri, 26 Jul 2024 18:30:59 +0100 Subject: [PATCH] feat:implement google-mail template fix: trigger_new_email.go file --- internal/connectors/googlemail/lib.go | 1 + .../googlemail/operation_send_email.go | 6 - .../operation_send_email_template.go | 184 ++++++++++++++++++ internal/connectors/googlemail/shared.go | 18 ++ .../googlemail/trigger_new_email.go | 67 +++---- 5 files changed, 232 insertions(+), 44 deletions(-) create mode 100644 internal/connectors/googlemail/operation_send_email_template.go diff --git a/internal/connectors/googlemail/lib.go b/internal/connectors/googlemail/lib.go index bd7947d..e8992e6 100644 --- a/internal/connectors/googlemail/lib.go +++ b/internal/connectors/googlemail/lib.go @@ -19,6 +19,7 @@ func NewConnector() (*sdk.ConnectorPlugin, error) { NewGetMailByIDOperation(), NewSendMailOperation(), NewGetThreadOperation(), + NewSendTemplateMailOperation(), NewListMailsOperation(), }, }) diff --git a/internal/connectors/googlemail/operation_send_email.go b/internal/connectors/googlemail/operation_send_email.go index 2848c15..89a1f0c 100644 --- a/internal/connectors/googlemail/operation_send_email.go +++ b/internal/connectors/googlemail/operation_send_email.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "errors" "fmt" - "regexp" "strings" "google.golang.org/api/gmail/v1" @@ -126,11 +125,6 @@ func (c *SendMailOperation) Run(ctx *sdk.RunContext) (sdk.JSON, error) { return "Message sent successfully!", nil } -func isValidEmail(email string) bool { - emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) - return emailRegex.MatchString(email) -} - func (c *SendMailOperation) Test(ctx *sdk.RunContext) (sdk.JSON, error) { return c.Run(ctx) } diff --git a/internal/connectors/googlemail/operation_send_email_template.go b/internal/connectors/googlemail/operation_send_email_template.go new file mode 100644 index 0000000..681cf83 --- /dev/null +++ b/internal/connectors/googlemail/operation_send_email_template.go @@ -0,0 +1,184 @@ +package googlemail + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "strings" + + "google.golang.org/api/gmail/v1" + "google.golang.org/api/option" + + "github.com/wakflo/go-sdk/autoform" + sdk "github.com/wakflo/go-sdk/connector" + sdkcore "github.com/wakflo/go-sdk/core" +) + +type sendTemplateMailOperationProps struct { + To string `json:"to"` + TemplateSubject string `json:"temp-subject"` + Subject string `json:"subject"` + Body string `json:"body"` + CC string `json:"cc"` + BCC string `json:"bcc"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + FromName string `json:"from"` +} + +type SendTemplateMailOperation struct { + options *sdk.OperationInfo +} + +func NewSendTemplateMailOperation() sdk.IOperation { + return &SendTemplateMailOperation{ + options: &sdk.OperationInfo{ + Name: "Send Template Email", + Description: "Send an personalized email.", + RequireAuth: true, + Auth: sharedAuth, + Input: map[string]*sdkcore.AutoFormSchema{ + "temp-subject": autoform.NewShortTextField(). + SetDisplayName(" Template title"). + SetDescription("The template title."). + SetRequired(true). + Build(), + "subject": autoform.NewShortTextField(). + SetDisplayName(" Subject"). + SetDescription("The template subject you want to send."). + SetRequired(true). + Build(), + "to": autoform.NewShortTextField(). + SetDisplayName(" To"). + SetDescription("The receiver of the message address"). + SetRequired(true). + Build(), + "from": autoform.NewShortTextField(). + SetDisplayName(" From"). + SetDescription("The sender of the message address"). + SetRequired(false). + Build(), + "firstName": autoform.NewShortTextField(). + SetDisplayName("First Name"). + SetDescription("First name"). + SetRequired(true). + Build(), + "lastName": autoform.NewShortTextField(). + SetDisplayName("Last Name"). + SetDescription("Last name"). + SetRequired(false). + Build(), + }, + ErrorSettings: sdkcore.StepErrorSettings{ + ContinueOnError: false, + RetryOnError: false, + }, + }, + } +} + +func (c *SendTemplateMailOperation) Run(ctx *sdk.RunContext) (sdk.JSON, error) { + if ctx.Auth.Token == nil { + return nil, errors.New("missing google auth token") + } + + input := sdk.InputToType[sendTemplateMailOperationProps](ctx) + gmailService, err := gmail.NewService(context.Background(), option.WithTokenSource(*ctx.Auth.TokenSource)) + if err != nil { + return nil, err + } + + userProfile, err := gmailService.Users.GetProfile("me").Do() + if err != nil { + return nil, err + } + userEmail := userProfile.EmailAddress + + if !isValidEmail(input.To) { + return nil, fmt.Errorf("invalid To email address: %s", input.To) + } + + bccEmails := strings.Split(input.BCC, ",") + for _, email := range bccEmails { + email = strings.TrimSpace(email) + if email != "" && !isValidEmail(email) { + return nil, fmt.Errorf("invalid BCC email address: %s", email) + } + } + + ccEmails := strings.Split(input.CC, ",") + for _, email := range ccEmails { + email = strings.TrimSpace(email) + if email != "" && !isValidEmail(email) { + return nil, fmt.Errorf("invalid CC email address: %s", email) + } + } + + // Search for the template message + query := "subject:" + input.TemplateSubject + searchResult, err := gmailService.Users.Messages.List("me").Q(query).Do() + if err != nil { + return nil, errors.New("error searching for template") + } + if len(searchResult.Messages) == 0 { + return nil, errors.New("no template found with subject: " + input.TemplateSubject) + } + + // Use the first matching message as the template + templateMsg, err := gmailService.Users.Messages.Get("me", searchResult.Messages[0].Id).Do() + if err != nil { + return nil, errors.New("error fetching template message") + } + + // Decode the email body + var emailBody string + for _, part := range templateMsg.Payload.Parts { + if part.MimeType == "text/html" { + data, _ := base64.URLEncoding.DecodeString(part.Body.Data) + emailBody = string(data) + break + } + } + if emailBody == "" { + return nil, errors.New("no HTML body found in template message") + } + + emailBody = strings.ReplaceAll(emailBody, "{{FirstName}}", input.FirstName) + emailBody = strings.ReplaceAll(emailBody, "{{LastName}}", input.LastName) + + var fromField string + if input.FromName != "" { + fromField = fmt.Sprintf("%s <%s>", input.FromName, userEmail) + } else { + fromField = userEmail + } + + // Use the modified template as the email body + message := &gmail.Message{ + Raw: base64.URLEncoding.EncodeToString([]byte( + "From: " + fromField + "\r\n" + + "To: " + input.To + "\r\n" + + "Subject: " + input.TemplateSubject + "\r\n" + + "Content-Type: text/html; charset=UTF-8\r\n\r\n" + + emailBody)), + } + + // Send the message + _, err = gmailService.Users.Messages.Send("me", message).Do() + if err != nil { + return nil, err + } + + return map[string]interface{}{ + "status": "Message sent successfully!", + }, nil +} + +func (c *SendTemplateMailOperation) Test(ctx *sdk.RunContext) (sdk.JSON, error) { + return c.Run(ctx) +} + +func (c *SendTemplateMailOperation) GetInfo() *sdk.OperationInfo { + return c.options +} diff --git a/internal/connectors/googlemail/shared.go b/internal/connectors/googlemail/shared.go index d50da8a..d669d8c 100644 --- a/internal/connectors/googlemail/shared.go +++ b/internal/connectors/googlemail/shared.go @@ -1,6 +1,10 @@ package googlemail import ( + "regexp" + + "google.golang.org/api/gmail/v1" + "github.com/wakflo/go-sdk/autoform" sdkcore "github.com/wakflo/go-sdk/core" ) @@ -19,3 +23,17 @@ var viewMailFormat = []*sdkcore.AutoFormSchema{ {Const: "raw", Title: "Raw"}, {Const: "metadata", Title: "Metadata"}, } + +func isValidEmail(email string) bool { + emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) + return emailRegex.MatchString(email) +} + +func getHeader(headers []*gmail.MessagePartHeader, name string) string { + for _, header := range headers { + if header.Name == name { + return header.Value + } + } + return "" +} diff --git a/internal/connectors/googlemail/trigger_new_email.go b/internal/connectors/googlemail/trigger_new_email.go index 21a8329..5c0c274 100644 --- a/internal/connectors/googlemail/trigger_new_email.go +++ b/internal/connectors/googlemail/trigger_new_email.go @@ -3,8 +3,6 @@ package googlemail import ( "context" "errors" - "fmt" - "strings" "time" "google.golang.org/api/gmail/v1" @@ -15,19 +13,11 @@ import ( sdkcore "github.com/wakflo/go-sdk/core" ) -type triggerNewEmailProps struct { - Subject string `json:"subject"` - From string `json:"from"` - MaxResults int `json:"maxResults"` - RecievedTime *time.Time `json:"receivedTime"` - RecievedTimeOp *string `json:"receivedTimeOp"` -} - type TriggerNewEmail struct { options *sdk.TriggerInfo } -func NewTriggerNewEmail() sdk.ITrigger { +func NewTriggerNewEmail() *TriggerNewEmail { return &TriggerNewEmail{ options: &sdk.TriggerInfo{ Name: "New Email ", @@ -67,55 +57,56 @@ func (t *TriggerNewEmail) Run(ctx *sdk.RunContext) (sdk.JSON, error) { return nil, errors.New("missing google auth token") } - input := sdk.InputToType[triggerNewEmailProps](ctx) gmailService, err := gmail.NewService(context.Background(), option.WithTokenSource(*ctx.Auth.TokenSource)) if err != nil { return nil, err } - var qarr []string - if input.Subject != "" { - qarr = append(qarr, fmt.Sprintf("subject:%v", input.Subject)) - } - if input.From != "" { - qarr = append(qarr, fmt.Sprintf("from:%v", input.From)) - } - if input.RecievedTime != nil { - input.RecievedTime = ctx.Metadata.LastRun - } - if input.RecievedTime != nil { - op := ">" - if input.RecievedTimeOp != nil { - op = *input.RecievedTimeOp - } - qarr = append(qarr, fmt.Sprintf(`received time %v '%v'`, op, input.RecievedTime.UTC().Format("2006-01-02T15:04:05Z"))) + var lastRunTime time.Time + if ctx.Metadata.LastRun != nil { + lastRunTime = *ctx.Metadata.LastRun } - q := strings.Join(qarr, " ") + query := "after:" + lastRunTime.Format("2006/01/02") - req := gmailService.Users.Messages.List("me"). - Q(q).MaxResults(int64(input.MaxResults)) - - res, err := req.Do() + messages, err := gmailService.Users.Messages.List("me").Q(query).Do() if err != nil { return nil, err } - return res.Messages, nil + newEmails := make([]map[string]interface{}, 0, len(messages.Messages)) + for _, msg := range messages.Messages { + email, err := gmailService.Users.Messages.Get("me", msg.Id).Do() + if err != nil { + continue + } + + emailData := map[string]interface{}{ + "id": email.Id, + "subject": getHeader(email.Payload.Headers, "Subject"), + "from": getHeader(email.Payload.Headers, "From"), + "date": getHeader(email.Payload.Headers, "Date"), + } + newEmails = append(newEmails, emailData) + } + + return sdk.JSON(map[string]interface{}{ + "new_emails": newEmails, + }), nil } -func (t TriggerNewEmail) Test(ctx *sdk.RunContext) (sdk.JSON, error) { +func (t *TriggerNewEmail) Test(ctx *sdk.RunContext) (sdk.JSON, error) { return t.Run(ctx) } -func (t TriggerNewEmail) OnEnabled(ctx *sdk.RunContext) error { +func (t *TriggerNewEmail) OnEnabled(ctx *sdk.RunContext) error { return nil } -func (t TriggerNewEmail) OnDisabled(ctx *sdk.RunContext) error { +func (t *TriggerNewEmail) OnDisabled(ctx *sdk.RunContext) error { return nil } -func (t TriggerNewEmail) GetInfo() *sdk.TriggerInfo { +func (t *TriggerNewEmail) GetInfo() *sdk.TriggerInfo { return t.options }