2

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?

Rubén
  • 34,714
  • 9
  • 70
  • 166

1 Answers1

7

TL;DR

  1. You are fusing two different triggers into one.
  2. Compose UI event object does not have a body field.
  3. Compose UI event object should not have a 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).

  • 1
    Hey I appreciate your detailed explanation. Are you saying to use the draft workaround to get the body of ‘your’ message in the compose window? I’m trying to body off the actual message in the open thread through compose ui is that possible somehow with a workaround? – Jason Silver Sep 19 '22 at 14:00
  • @JasonSilver it depends on what you mean by the "actual message" - could you clarify this a little? If it is the message the user is currently composing, then yes, using the workaround one can (reasonably) get the currently composed message as a `GmailDraft` object which has the necessary method to get the body (`getBody`). If, however, the goal is to get the body of the opened message to which the draft is a reply, for example, then it becomes a bit more complicated, you'll probably want to add a message UI trigger and store the opened message id in the state (f.e., via `PropertiesService`). – Oleg Valter is with Ukraine Sep 20 '22 at 13:37
  • it is the latter (the body of the recipient's message). I have setup the message ui i think where the icon displays on the right menu bar in gmail which works fine. However i also have the compose trigger setup which shows the add-on button in the compose bottom bar which i want to retrieve the message id that way but the event payload doesn't have it. The only thing i can use is the subject and recipient to try search and match the thread but its not very reliable – Jason Silver Sep 22 '22 at 00:19