I am working on a Google Workspace Add-on for Gmail. The add-on is being used to help create pre-defined filters given existing messages. I am using the users.settings.filters
api to create the filters.
Natively, in Gmail, when you create a filter there is an options to "Apply filter to X existing messages" but this functionality does not appear to be provided through the API.
The problem I am facing is the fact that in order to recreate this functionality myself, the execution gets timed out by App Script for a large number of messages due to the 30sec constraint.
Currently, I am using methods like GmailApp.moveThreadsToInbox
and label.addToThreads
. Is there a better method?
If there is not a better method, how can I improve the implementation? I have considered creating timed triggers to process large quantities but the documentation surrounding this is unclear on limitation and quotas so I would appreciate clarification there as well.
Here is a code snippet of my current implementation:
function applyActionToThreads(threads, action) {
// Add/Remove labels to emails
// NOTE: Add/Remove array order matters to keep this functional.
// i.e remove = ["INBOX", "SPAM"] will move to archive then inbox again
// rather than ["SPAM', "INBOX"] will move to inbox then archive.
const existingLabels = Gmail.Users.Labels.list("me").labels;
const actionLabels = [];
actionLabels.push(
...action.addLabelIds.map((lbl) => {
return { id: lbl, remove: false };
})
);
actionLabels.push(
...action.removeLabelIds.map((lbl) => {
return { id: lbl, remove: true };
})
);
for (const label of actionLabels) {
switch (label.id) {
case "INBOX":
if (label.remove) {
GmailApp.moveThreadsToArchive(threads);
} else {
GmailApp.moveThreadsToInbox(threads);
}
break;
case "SPAM":
if (label.remove) {
GmailApp.moveThreadsToInbox(threads);
} else {
GmailApp.moveThreadsToSpam(threads);
}
break;
case "TRASH":
if (label.remove) {
GmailApp.moveThreadsToInbox(threads);
} else {
GmailApp.moveThreadsToTrash(threads);
}
break;
case "UNREAD":
if (label.remove) {
GmailApp.markThreadsRead(threads);
} else {
GmailApp.markThreadsUnread(threads);
}
break;
default:
// Not a system label
console.log(label);
console.log(existingLabels);
const userLabel = GmailApp.getUserLabelByName(
existingLabels.find((l) => l.id == label.id).name
);
for (let i = 0; i < threads.length; i += 99) {
if (label.remove) {
userLabel.removeFromThreads(threads.slice(i, i + 100));
} else {
userLabel.addToThreads(threads.slice(i, i + 100));
}
}
}
}
}
I overlooked users.messages.batchModify in docs.
// Apply actions to each batch
Gmail.Users.Messages.batchModify(
{
ids: batch.map((message) => message.id),
addLabelIds: filter.action.addLabelIds,
removeLabelIds: filter.action.removeLabelIds,
},
"me"
);
}