Reputation: 23
I have a similar problem as this Question and that subject bug is already solved, but I need to get the "Body" from the message the user is writing, and also the "messageId".
I have one contextualTrigger but it doesn’t log on STACKDRIVER
function onGmailMessageOpen(e) {
console.log(e);
// Get the ID of the message the user has open.
var messageId = e.gmail.messageId;
}
"gmail": {
"contextualTriggers": [
{
"unconditional": {},
"onTriggerFunction": "onGmailMessageOpen"
}
],
Is there any way to get the "body" and "messageId" of current composing message, regardless if its a new draft or a reply, or is this a limitation of the addon?
Upvotes: 2
Views: 1075
Reputation: 10345
TL;DR
body
field.subject
field (but has).This is not a bug
This behaviour is how event objects for triggers firing in the context of the compose UI were supposed to work. In the documentation, there is no mention of either subject
or body
field (despite subject
now being available de facto, probably as a result of the feature request mentioned in the Q&A you referenced).
Event object structure
Presently, gmail
resource can only have the following properties:
| Property | Type | Always present? |
| ------------- | -------- | ------------------- |
| accessToken | string | Yes |
| bccRecipients | string[] | disabled by default |
| ccRecipients | string[] | disabled by default |
| messageId | string | Yes |
| threadId | string | Yes |
| toRecipients | string[] | disabled by default |
However, this event object structure is specific to the message UI and is not constructed in full in the compose UI context.
Compose UI event object
The compose UI trigger specified in the composeTrigger
manifest field does not have access to the open message metadata. Given the METADATA
scope is present, the event object looks like this (if the subject
is empty, it will be missing from the resource):
{
commonEventObject: {
platform: 'WEB',
hostApp: 'GMAIL'
},
gmail: {
subject: '12345'
},
clientPlatform: 'web',
draftMetadata: {
toRecipients: [],
subject: '12345',
bccRecipients: [],
ccRecipients: []
},
hostApp: 'gmail'
}
Now, try building a Card
and add an actionable widget to it (i.e. a TextButton
):
const onComposeAction = (e) => {
const builder = CardService.newCardBuilder();
const section = CardService.newCardSection();
const action = CardService.newAction();
action.setFunctionName("handleButtonClick"); //<-- callback name to test event object;
const widget = CardService.newTextButton();
widget.setText("Test Event Object");
widget.setOnClickAction(action);
section.addWidget(widget);
builder.addSection(section);
return builder.build();
};
Upon triggering the action, if you log the event object, you will see that it looks pretty similar to the previous one with action event object properties attached:
{
hostApp: 'gmail',
formInputs: {}, //<-- multi-value inputs
formInput: {}, //<-- single-value inputs
draftMetadata: {
subject: '12345',
ccRecipients: [],
toRecipients: [],
bccRecipients: []
},
gmail: {
subject: '12345'
},
parameters: {}, //<-- parameters passed to builder
clientPlatform: 'web',
commonEventObject: {
hostApp: 'GMAIL',
platform: 'WEB'
}
}
Note the absence of accessToken
, threadId
, and messageId
properties - the trigger fires in the context of the currently open draft, not the open email.
Message UI event object
On the contrary, the message UI event object (the one constructed in response to opening an email in reading mode and passed to a function specified in onTriggerFunction
manifest property) does contain the necessary metadata:
{
messageMetadata: {
accessToken: 'token here',
threadId: 'thread id here',
messageId: 'message id here'
},
clientPlatform: 'web',
gmail: {
messageId: 'message id here',
threadId: 'thread id here',
accessToken: 'token here'
},
commonEventObject: {
platform: 'WEB',
hostApp: 'GMAIL'
},
hostApp: 'gmail'
}
Workaround
A viable workaround is to use the getDraftMessages
method and extract the first matching draft (reasonably assuming that in the meantime a full duplicate of the draft is not created). An example of such a utility would be:
const listDraftGmailMessages = ({
subject,
toRecipients: to,
ccRecipients: cc,
bccRecipients: bcc
} = {}) => {
const drafts = GmailApp.getDraftMessages();
return drafts.filter((draft) => {
const s = draft.getSubject();
const t = draft.getTo().split(",");
const c = draft.getCc().split(",");
const b = draft.getBcc().split(",");
const sameSubj = subject ? s === subject : true;
const sameTo = to ? t.every(r => to.includes(trimFrom(r))) : true;
const sameCc = cc ? c.every(r => cc.includes(trimFrom(r))) : true;
const sameBcc = bcc ? b.every(r => bcc.includes(trimFrom(r))) : true;
return sameSubj && sameTo && sameCc && sameBcc;
});
};
Note that getTo
, getCc
and getBcc
all return recipients in the form of name <email>
, so they have to be trimmed. A "good enough" utility trimFrom
should do the trick:
const trimFrom = (input) => {
try {
const regex = /<([-\w.]+@\w+(?:\.\w+)+)>/i;
const [, email] = input.match(regex) || [input];
return email || input;
} catch (error) {
console.warn(error);
return input;
}
};
After extracting the first matching draft, you can do with it as you wish (in your case, use the getBody
method).
Upvotes: 7