diff --git a/packages/backend/src/apps/gmail/actions/create-draft/index.js b/packages/backend/src/apps/gmail/actions/create-draft/index.js new file mode 100644 index 00000000..e42c772a --- /dev/null +++ b/packages/backend/src/apps/gmail/actions/create-draft/index.js @@ -0,0 +1,204 @@ +import defineAction from '../../../../helpers/define-action.js'; + +export default defineAction({ + name: 'Create draft', + key: 'createDraft', + description: 'Create a new draft email message.', + arguments: [ + { + label: 'Subject', + key: 'subject', + type: 'string', + required: true, + description: '', + variables: true, + }, + { + label: 'TOs', + key: 'tos', + type: 'dynamic', + required: false, + description: '', + fields: [ + { + label: 'To', + key: 'to', + type: 'string', + required: false, + variables: true, + }, + ], + }, + { + label: 'CCs', + key: 'ccs', + type: 'dynamic', + required: false, + description: '', + fields: [ + { + label: 'CC', + key: 'cc', + type: 'string', + required: false, + variables: true, + }, + ], + }, + { + label: 'BCCs', + key: 'bccs', + type: 'dynamic', + required: false, + description: '', + fields: [ + { + label: 'BCC', + key: 'bcc', + type: 'string', + required: false, + variables: true, + }, + ], + }, + { + label: 'From', + key: 'from', + type: 'dropdown', + required: false, + description: + 'Select an email address or alias from your Gmail Account. Defaults to the primary email address.', + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listEmails', + }, + ], + }, + }, + { + label: 'From Name', + key: 'fromName', + type: 'string', + required: false, + description: '', + variables: true, + }, + { + label: 'Body Type', + key: 'bodyType', + type: 'dropdown', + required: false, + description: '', + variables: true, + options: [ + { + label: 'plain', + value: 'plain', + }, + { + label: 'html', + value: 'html', + }, + ], + }, + { + label: 'Body', + key: 'emailBody', + type: 'string', + required: true, + description: '', + variables: true, + }, + { + label: 'Signature', + key: 'signature', + type: 'dropdown', + required: false, + description: '', + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listSignatures', + }, + ], + }, + }, + ], + + async run($) { + const { + tos, + ccs, + bccs, + from, + fromName, + subject, + bodyType, + emailBody, + signature, + } = $.step.parameters; + const userId = $.auth.data.userId; + + const allTos = tos?.map((entry) => entry.to); + const allCcs = ccs?.map((entry) => entry.cc); + const allBccs = bccs?.map((entry) => entry.bcc); + const contentType = + bodyType === 'html' + ? 'text/html; charset="UTF-8"' + : 'text/plain; charset="UTF-8"'; + + const email = + 'From: ' + + fromName + + ' <' + + from + + '>' + + '\r\n' + + 'To: ' + + allTos.join(',') + + '\r\n' + + 'Cc: ' + + allCcs.join(',') + + '\r\n' + + 'Bcc: ' + + allBccs.join(',') + + '\r\n' + + 'Subject: ' + + subject + + '\r\n' + + 'Content-Type: ' + + contentType + + '\r\n' + + '\r\n' + + emailBody + + '\r\n' + + '\r\n' + + signature; + + const base64EncodedEmailBody = Buffer.from(email).toString('base64'); + + const body = { + message: { + raw: base64EncodedEmailBody, + }, + }; + + const { data } = await $.http.post( + `/gmail/v1/users/${userId}/drafts`, + body + ); + + $.setActionItem({ + raw: data, + }); + }, +}); diff --git a/packages/backend/src/apps/gmail/actions/index.js b/packages/backend/src/apps/gmail/actions/index.js index e1d8a996..28b72220 100644 --- a/packages/backend/src/apps/gmail/actions/index.js +++ b/packages/backend/src/apps/gmail/actions/index.js @@ -1,3 +1,7 @@ +import createDraft from './create-draft/index.js'; +import replyToEmail from './reply-to-email/index.js'; import sendEmail from './send-email/index.js'; +import sendToTrash from './send-to-trash/index.js'; +import starEmail from './star-email/index.js'; -export default [sendEmail]; +export default [createDraft, replyToEmail, sendEmail, sendToTrash, starEmail]; diff --git a/packages/backend/src/apps/gmail/actions/reply-to-email/index.js b/packages/backend/src/apps/gmail/actions/reply-to-email/index.js new file mode 100644 index 00000000..5585e6b1 --- /dev/null +++ b/packages/backend/src/apps/gmail/actions/reply-to-email/index.js @@ -0,0 +1,228 @@ +import defineAction from '../../../../helpers/define-action.js'; + +export default defineAction({ + name: 'Reply to email', + key: 'replyToEmail', + description: 'Respond to an email.', + arguments: [ + { + label: 'Thread', + key: 'threadId', + type: 'dropdown', + required: false, + description: '', + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listThreads', + }, + ], + }, + }, + { + label: 'TOs', + key: 'tos', + type: 'dynamic', + required: false, + description: 'Who will receive this email?', + fields: [ + { + label: 'To', + key: 'to', + type: 'string', + required: false, + variables: true, + }, + ], + }, + { + label: 'CCs', + key: 'ccs', + type: 'dynamic', + required: false, + description: + 'Who else needs to be included in the CC field of this email?', + fields: [ + { + label: 'CC', + key: 'cc', + type: 'string', + required: false, + variables: true, + }, + ], + }, + { + label: 'BCCs', + key: 'bccs', + type: 'dynamic', + required: false, + description: + 'Who else needs to be included in the BCC field of this email?', + fields: [ + { + label: 'BCC', + key: 'bcc', + type: 'string', + required: false, + variables: true, + }, + ], + }, + { + label: 'From', + key: 'from', + type: 'dropdown', + required: false, + description: + 'Choose an email address or alias from your Gmail Account. This defaults to the primary email address.', + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listEmails', + }, + ], + }, + }, + { + label: 'From Name', + key: 'fromName', + type: 'string', + required: false, + description: '', + variables: true, + }, + { + label: 'Reply To', + key: 'replyTo', + type: 'string', + required: false, + description: 'Specify a single reply address other than your own.', + variables: true, + }, + { + label: 'Body Type', + key: 'bodyType', + type: 'dropdown', + required: false, + description: '', + variables: true, + options: [ + { + label: 'plain', + value: 'plain', + }, + { + label: 'html', + value: 'html', + }, + ], + }, + { + label: 'Body', + key: 'emailBody', + type: 'string', + required: true, + description: '', + variables: true, + }, + { + label: 'Label', + key: 'labelId', + type: 'dropdown', + required: false, + description: '', + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listLabels', + }, + ], + }, + }, + ], + + async run($) { + const { + tos, + ccs, + bccs, + from, + fromName, + replyTo, + threadId, + bodyType, + emailBody, + labelId, + } = $.step.parameters; + const userId = $.auth.data.userId; + + const allTos = tos?.map((entry) => entry.to); + const allCcs = ccs?.map((entry) => entry.cc); + const allBccs = bccs?.map((entry) => entry.bcc); + const contentType = + bodyType === 'html' + ? 'text/html; charset="UTF-8"' + : 'text/plain; charset="UTF-8"'; + + const email = + 'From: ' + + fromName + + ' <' + + from + + '>' + + '\r\n' + + 'In-Reply-To: ' + + threadId + + '\r\n' + + 'References: ' + + threadId + + '\r\n' + + 'Reply-To: ' + + replyTo + + '\r\n' + + 'To: ' + + allTos.join(',') + + '\r\n' + + 'Cc: ' + + allCcs.join(',') + + '\r\n' + + 'Bcc: ' + + allBccs.join(',') + + '\r\n' + + 'Content-Type: ' + + contentType + + '\r\n' + + '\r\n' + + emailBody; + + const base64EncodedEmailBody = Buffer.from(email).toString('base64'); + + const body = { + threadId: threadId, + labelIds: [labelId], + raw: base64EncodedEmailBody, + }; + + const { data } = await $.http.post( + `/gmail/v1/users/${userId}/messages/send`, + body + ); + + $.setActionItem({ + raw: data, + }); + }, +}); diff --git a/packages/backend/src/apps/gmail/actions/send-to-trash/index.js b/packages/backend/src/apps/gmail/actions/send-to-trash/index.js new file mode 100644 index 00000000..60587635 --- /dev/null +++ b/packages/backend/src/apps/gmail/actions/send-to-trash/index.js @@ -0,0 +1,40 @@ +import defineAction from '../../../../helpers/define-action.js'; + +export default defineAction({ + name: 'Send to trash', + key: 'sendToTrash', + description: 'Send an existing email message to the trash.', + arguments: [ + { + label: 'Message ID', + key: 'messageId', + type: 'dropdown', + required: true, + description: '', + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listMessages', + }, + ], + }, + }, + ], + + async run($) { + const { messageId } = $.step.parameters; + const userId = $.auth.data.userId; + + const { data } = await $.http.post( + `/gmail/v1/users/${userId}/messages/${messageId}/trash` + ); + + $.setActionItem({ + raw: data, + }); + }, +}); diff --git a/packages/backend/src/apps/gmail/actions/star-email/index.js b/packages/backend/src/apps/gmail/actions/star-email/index.js new file mode 100644 index 00000000..c7e06bcc --- /dev/null +++ b/packages/backend/src/apps/gmail/actions/star-email/index.js @@ -0,0 +1,45 @@ +import defineAction from '../../../../helpers/define-action.js'; + +export default defineAction({ + name: 'Star an email', + key: 'starEmail', + description: 'Star an email message.', + arguments: [ + { + label: 'Message ID', + key: 'messageId', + type: 'dropdown', + required: true, + description: '', + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listMessages', + }, + ], + }, + }, + ], + + async run($) { + const { messageId } = $.step.parameters; + const userId = $.auth.data.userId; + + const body = { + addLabelIds: ['STARRED'], + }; + + const { data } = await $.http.post( + `/gmail/v1/users/${userId}/messages/${messageId}/modify`, + body + ); + + $.setActionItem({ + raw: data, + }); + }, +}); diff --git a/packages/backend/src/apps/gmail/auth/refresh-token.js b/packages/backend/src/apps/gmail/auth/refresh-token.js index 7c5b7020..2c137caa 100644 --- a/packages/backend/src/apps/gmail/auth/refresh-token.js +++ b/packages/backend/src/apps/gmail/auth/refresh-token.js @@ -12,7 +12,12 @@ const refreshToken = async ($) => { const { data } = await $.http.post( 'https://oauth2.googleapis.com/token', - params.toString() + params.toString(), + { + additionalProperties: { + skipAddingAuthHeader: true, + }, + } ); await $.auth.set({ diff --git a/packages/backend/src/apps/gmail/common/add-auth-header.js b/packages/backend/src/apps/gmail/common/add-auth-header.js index 02477aa4..f957ebf9 100644 --- a/packages/backend/src/apps/gmail/common/add-auth-header.js +++ b/packages/backend/src/apps/gmail/common/add-auth-header.js @@ -1,5 +1,8 @@ const addAuthHeader = ($, requestConfig) => { - if ($.auth.data?.accessToken) { + if ( + !requestConfig.additionalProperties?.skipAddingAuthHeader && + $.auth.data?.accessToken + ) { requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`; } diff --git a/packages/backend/src/apps/gmail/dynamic-data/index.js b/packages/backend/src/apps/gmail/dynamic-data/index.js index 46af300e..37661b45 100644 --- a/packages/backend/src/apps/gmail/dynamic-data/index.js +++ b/packages/backend/src/apps/gmail/dynamic-data/index.js @@ -1,5 +1,13 @@ import listEmails from './list-emails/index.js'; import listLabels from './list-labels/index.js'; +import listMessages from './list-messages/index.js'; import listSignatures from './list-signatures/index.js'; +import listThreads from './list-threads/index.js'; -export default [listEmails, listLabels, listSignatures]; +export default [ + listEmails, + listLabels, + listMessages, + listSignatures, + listThreads, +]; diff --git a/packages/backend/src/apps/gmail/dynamic-data/list-messages/index.js b/packages/backend/src/apps/gmail/dynamic-data/list-messages/index.js new file mode 100644 index 00000000..077267be --- /dev/null +++ b/packages/backend/src/apps/gmail/dynamic-data/list-messages/index.js @@ -0,0 +1,31 @@ +export default { + name: 'List messages', + key: 'listMessages', + + async run($) { + const messages = { + data: [], + }; + const userId = $.auth.data.userId; + + const { data } = await $.http.get(`/gmail/v1/users/${userId}/messages`); + + if (data.messages) { + for (const message of data.messages) { + const { data: messageData } = await $.http.get( + `/gmail/v1/users/${userId}/messages/${message.id}` + ); + const subject = messageData.payload.headers.find( + (header) => header.name === 'Subject' + ); + + messages.data.push({ + value: message.id, + name: subject?.value, + }); + } + } + + return messages; + }, +}; diff --git a/packages/backend/src/apps/gmail/dynamic-data/list-threads/index.js b/packages/backend/src/apps/gmail/dynamic-data/list-threads/index.js new file mode 100644 index 00000000..6d40fa2f --- /dev/null +++ b/packages/backend/src/apps/gmail/dynamic-data/list-threads/index.js @@ -0,0 +1,31 @@ +export default { + name: 'List threads', + key: 'listThreads', + + async run($) { + const threads = { + data: [], + }; + const userId = $.auth.data.userId; + + const { data } = await $.http.get(`/gmail/v1/users/${userId}/threads`); + + if (data.threads) { + for (const thread of data.threads) { + const { data: threadData } = await $.http.get( + `/gmail/v1/users/${userId}/threads/${thread.id}` + ); + const subject = threadData.messages[0].payload.headers.find( + (header) => header.name === 'Subject' + ); + + threads.data.push({ + value: thread.id, + name: subject?.value, + }); + } + } + + return threads; + }, +}; diff --git a/packages/docs/pages/apps/gmail/actions.md b/packages/docs/pages/apps/gmail/actions.md index 2f12fe51..0754a728 100644 --- a/packages/docs/pages/apps/gmail/actions.md +++ b/packages/docs/pages/apps/gmail/actions.md @@ -1,8 +1,16 @@ --- favicon: /favicons/gmail.svg items: + - name: Create draft + desc: Create a new draft email message. + - name: Reply to email + desc: Respond to an email. - name: Send email desc: Send a new email message. + - name: Send to trash + desc: Send an existing email message to the trash. + - name: Star an email + desc: Star an email message. ---