Google Script Quota Issues: Gmail Read - google-apps-script

I'm having a bit of trouble with my google script code. I'm trying to make a script that will automatically take any email that is sent with 'Events' in the subject and parse the text to add it to the calendar. I have a filter that will add the email to the label 'Events', then remove the label.
The script is working fine and is posted below. I have it triggering every minute. However, every day I get a huge failure notification email that I've exceeded the quota for gmail reads on line 4 [GmailApp.getUserLabelbyName]. I tried googling and I couldn't find more specifics.
So, according to the quota website I have a limit of 10,000 reads per day. This has been failing with no emails at all in the 'Events' bin, so no actual emails appear to be read. The calendar should be accessing it once per minute, i.e. 1,440 times a day.
So why am I getting the quota failure for gmail read? Does 'gmail reads' mean accesses to gmail (getting the label variable) or reading email bodies (getThreads) or both?
Any help would be greatly appreciated. Thanks!
function MakeEvents() {
var label = GmailApp.getUserLabelByName("Events");
if (label) {
var threads = label.getThreads();
for (var x in threads) {
var messages = threads[x].getMessages();
for (var y in messages) {
var myHTMLContent = messages[y].getBody();
textContent=getTextFromNode(Xml.parse(myHTMLContent, true).getElement());
CalendarApp.getDefaultCalendar().createEventFromDescription(textContent);
}
threads[x].removeLabel(label);
}
}
}
function getTextFromNode(x) {
switch(x.toString()) {
case 'XmlText': return x.toXmlString();
case 'XmlElement': return x.getNodes().map(getTextFromNode).join('');
default: return '';
}
}

Related

Using Google Apps Script, how do you remove built in Gmail category labels from an email?

I'd like to completely undo any of Gmails built in category labels. This was my attempt.
function removeBuiltInLabels() {
var updatesLabel = GmailApp.getUserLabelByName("updates");
var socialLabel = GmailApp.getUserLabelByName("social");
var forumsLabel = GmailApp.getUserLabelByName("forums");
var promotionsLabel = GmailApp.getUserLabelByName("promotions");
var inboxThreads = GmailApp.search('in:inbox');
for (var i = 0; i < inboxThreads.length; i++) {
updatesLabel.removeFromThreads(inboxThreads[i]);
socialLabel.removeFromThreads(inboxThreads[i]);
forumsLabel.removeFromThreads(inboxThreads[i]);
promotionsLabel.removeFromThreads(inboxThreads[i]);
}
}
However, this throws....
TypeError: Cannot call method "removeFromThreads" of null.
It seems you can't access the built in labels in this way even though you can successfully search for label:updates in the Gmail search box and get the correct results.
The question...
How do you access the built in Gmail Category labels in Google Apps Script and remove them from an email/thread/threads?
Thanks.
'INBOX' and other system labels like 'CATEGORY_SOCIAL' can be removed using Advanced Gmail Service. In the Script Editor, go to Resources -> Advanced Google services and enable the Gmail service.
More details about naming conventions for system labels in Gmail can be found here Gmail API - Managing Labels
Retrieve the threads labeled with 'CATEGORY_SOCIAL' by calling the list() method of the threads collection:
var threads = Gmail.Users.Threads.list("me", {labels: ["CATEGORY_SOCIAL"]});
var threads = threads.threads;
var nextPageToken = threads.nextPageToken;
Note that you are going to need to store the 'nextPageToken' to iterate over the entire collection of threads. See this answer.
When you get all thread ids, you can call the 'modify()' method of the Threads collection on them:
threads.forEach(function(thread){
var resource = {
"addLabelIds": [],
"removeLabelIds":["CATEGORY_SOCIAL"]
};
Gmail.Users.Threads.modify(resource, "me", threadId);
});
If you have lots of threads in your inbox, you may still need to call the 'modify()' method several times and save state between calls.
Anton's answer is great. I marked it as accepted because it lead directly to the version I'm using.
This function lets you define any valid gmail search to isolate messages and enables batch removal labels.
function removeLabelsFromMessages(query, labelsToRemove) {
var foundThreads = Gmail.Users.Threads.list('me', {'q': query}).threads
if (foundThreads) {
foundThreads.forEach(function (thread) {
Gmail.Users.Threads.modify({removeLabelIds: labelsToRemove}, 'me', thread.id);
});
}
}
I call it via the one minute script trigger like this.
function ProcessInbox() {
removeLabelsFromMessages(
'label:updates OR label:social OR label:forums OR label:promotions',
['CATEGORY_UPDATES', 'CATEGORY_SOCIAL', 'CATEGORY_FORUMS', 'CATEGORY_PROMOTIONS']
)
<...other_stuff_to_process...>
}

GmailApp Won't Unstar Gmail Message [duplicate]

OK, I think web Gmail is being screwy. I run a Google apps script that in part adds my "To-do" label to any thread I manually star, then archives and unstars it. A snippet is below. I'd appreciate any help.
After running the script the thread gets the label and is unstarred, except the star icon next to the thread/message in web Gmail still shows as selected. If I go to Starred messages label/folder nothing is displayed. If I immediately rerun the script it doesn't find any Starred threads. This seems to indicate the script is working OK. The problem seems to be that web Gmail still wants to show it as Starred even though it isn't. Gmail Android app doesn't show the star applied to the thread. Pictures are worth more...
What my inbox looks like after the script runs. Note the star:
Yet no Starred messages:
function addStarred2ToDo() {
var threads = GmailApp.search('is:starred');
for (var h in threads) {
var messages = threads[h].getMessages();
for (var i in messages) {
if (messages[i].isStarred()) {
messages[i].unstar();
}
}
}
}
EDIT:
I also tried this and neither produce what is expected.
function thisOne() {
var threads = GmailApp.search('is:starred');
for (var h in threads) {
var messages = threads[h].getMessages();
for (var i in messages) {
if (messages[i].isStarred()) {
messages[i].unstar().refresh();
}
}
}
}
function andThisOne() {
var threads = GmailApp.search('is:starred');
var toUnstar = [];
threads.forEach(function (thread) {
thread.getMessages().forEach(function (message) {
if (message.isStarred()) {
toUnstar.push(message);
}
});
});
GmailApp.unstarMessages(toUnstar);
}
If you refresh gmail and hover over the star, you will see that the popup says it is Not Stared. Also, this seems to be an issue when you select the star from gmail, as stars that are set by my filters work correctly when my script unstars them.
This display issue is caused because you do not force Gmail to update the message with a call to refresh() after your call to unstar().
Per documentation of GmailMessage#refresh():
Reloads this message and associated state from Gmail (useful in case the labels, read state, etc., have changed).
messages[i].unstar().refresh();
Should be sufficient to inform Gmail of the new starred status.
Alternately, a batch call to modify the messages will be more efficient in terms of quota usage:
var toUnstar = [];
threads.forEach(function (thread) {
thread.getMessages().forEach(function (message) {
if (message.isStarred()) {
toUnstar.push(message);
...
}
});
});
GmailApp.unstarMessages(toUnstar);
In my sample I avoid the assumption that iterating an array is safe with for .. in.. and use the more expressive Array.forEach() to indicate that the code is something that we want to apply to every thread and every message in said thread.
Documentation on GmailApp.unstarMessages():
Removes stars from these messages and forces the messages to refresh.
I'm having a similar problem. I have enabled a superstar, the green-check. I manually set them.
My script finds the relevant threads using "l:^ss_cg" in a search. It finds the starred email, sends a copy of the email somewhere, and then does the unstar.
Afterward, in the web gmail, if i search for the same message again, it shows up with a green star visually, but if I hover over the star icon, it shows 'not starred'.
However, if I run the script again, the thread is found using the search. It doesn't send another copy of the email, however, because I have a check for ".isStarred()" before it sends a copy of the specific email. I have also been able to reduce the number of threads it double-checks by adding a .hasStarredEmails() check to the thread before it starts looking at individual emails.
Doing a search in the web interface for has:green-check reveals only the emails it should.
There is something about .unStar() that doesn't work properly, I think.
I had considered trying to remove the star at the thread level by removing the ^ss_cg label, but that won't work because there is no way to get a GMailLabel object to send to the function.
Still having same issue where the Gmail web app shows the star. But just made interesting finding.
Assuming
var threads = GmailApp.getStarredThreads()
var thread = threads[0]
var message = thread.getMessages()[0]
GmailApp.unstarMessage(message) would immediately give the following results:
var isMessageStarred = message.isStarred() true
var isThreadStarred = thread.hasStarredMessages() true
GmailApp.unstarMessage(message).refreshMessage(message) would immediately give the following results:
var isMessageStarred = message.isStarred() false
var isThreadStarred = thread.hasStarredMessages() true
And GmailApp.unstarMessage(message).refreshMessage(message).refreshThread(thread) would immediately give the following results:
var isMessageStarred = message.isStarred() false
var isThreadStarred = thread.hasStarredMessages() false
I had this case. Actually what was happening was I was starring everything that hit the inbox with a filter. Once my script processed the message I unstarred it. But my script was also forwarding the message and that new sent message was being starred by the filter. So the thread still contained a starred message. Argh.
My solution was to create another filter to delete any messages from me.
Same problem here.
I am unable to unstar() emails which I starred manually in my browser/on my phone.
If I star an email via a rule or a script and then unstar it with a gapps script, unstarring works.
I also tried doing refresh right after unstarring - no difference:
message.unstar()
message.refresh()
I wrote a script which stars always the last message of a previously starred thread to keep my starred threads with the most recent replies at the top of my mailbox. The only problem is that it's not really working if I star an email manually at the beginning and not with a rule or another script.
The code going through my threads:
for(var z=0;z<messages.length;z++){
var message = messages[z]
var messageTime = message.getDate()
var messageTimeInCET = Utilities.formatDate(messageTime,'CET','yyyy-MM-dd HH:mm:ss')
//Logger.log('messageTime: '+ messageTime)
//Let's add star to the last email in the thread and remove stars from all previous emails
if(threadLastMessageDateInCET==messageTimeInCET){
Logger.log('Starred email from: '+ messageTimeInCET)
message.star()
}
else{
message.unstar()
message.refresh()
Logger.log('Unstarred email from: '+messageTimeInCET)
}
}

Web Gmail shows message as starred even after Apps Script unstars it

OK, I think web Gmail is being screwy. I run a Google apps script that in part adds my "To-do" label to any thread I manually star, then archives and unstars it. A snippet is below. I'd appreciate any help.
After running the script the thread gets the label and is unstarred, except the star icon next to the thread/message in web Gmail still shows as selected. If I go to Starred messages label/folder nothing is displayed. If I immediately rerun the script it doesn't find any Starred threads. This seems to indicate the script is working OK. The problem seems to be that web Gmail still wants to show it as Starred even though it isn't. Gmail Android app doesn't show the star applied to the thread. Pictures are worth more...
What my inbox looks like after the script runs. Note the star:
Yet no Starred messages:
function addStarred2ToDo() {
var threads = GmailApp.search('is:starred');
for (var h in threads) {
var messages = threads[h].getMessages();
for (var i in messages) {
if (messages[i].isStarred()) {
messages[i].unstar();
}
}
}
}
EDIT:
I also tried this and neither produce what is expected.
function thisOne() {
var threads = GmailApp.search('is:starred');
for (var h in threads) {
var messages = threads[h].getMessages();
for (var i in messages) {
if (messages[i].isStarred()) {
messages[i].unstar().refresh();
}
}
}
}
function andThisOne() {
var threads = GmailApp.search('is:starred');
var toUnstar = [];
threads.forEach(function (thread) {
thread.getMessages().forEach(function (message) {
if (message.isStarred()) {
toUnstar.push(message);
}
});
});
GmailApp.unstarMessages(toUnstar);
}
If you refresh gmail and hover over the star, you will see that the popup says it is Not Stared. Also, this seems to be an issue when you select the star from gmail, as stars that are set by my filters work correctly when my script unstars them.
This display issue is caused because you do not force Gmail to update the message with a call to refresh() after your call to unstar().
Per documentation of GmailMessage#refresh():
Reloads this message and associated state from Gmail (useful in case the labels, read state, etc., have changed).
messages[i].unstar().refresh();
Should be sufficient to inform Gmail of the new starred status.
Alternately, a batch call to modify the messages will be more efficient in terms of quota usage:
var toUnstar = [];
threads.forEach(function (thread) {
thread.getMessages().forEach(function (message) {
if (message.isStarred()) {
toUnstar.push(message);
...
}
});
});
GmailApp.unstarMessages(toUnstar);
In my sample I avoid the assumption that iterating an array is safe with for .. in.. and use the more expressive Array.forEach() to indicate that the code is something that we want to apply to every thread and every message in said thread.
Documentation on GmailApp.unstarMessages():
Removes stars from these messages and forces the messages to refresh.
I'm having a similar problem. I have enabled a superstar, the green-check. I manually set them.
My script finds the relevant threads using "l:^ss_cg" in a search. It finds the starred email, sends a copy of the email somewhere, and then does the unstar.
Afterward, in the web gmail, if i search for the same message again, it shows up with a green star visually, but if I hover over the star icon, it shows 'not starred'.
However, if I run the script again, the thread is found using the search. It doesn't send another copy of the email, however, because I have a check for ".isStarred()" before it sends a copy of the specific email. I have also been able to reduce the number of threads it double-checks by adding a .hasStarredEmails() check to the thread before it starts looking at individual emails.
Doing a search in the web interface for has:green-check reveals only the emails it should.
There is something about .unStar() that doesn't work properly, I think.
I had considered trying to remove the star at the thread level by removing the ^ss_cg label, but that won't work because there is no way to get a GMailLabel object to send to the function.
Still having same issue where the Gmail web app shows the star. But just made interesting finding.
Assuming
var threads = GmailApp.getStarredThreads()
var thread = threads[0]
var message = thread.getMessages()[0]
GmailApp.unstarMessage(message) would immediately give the following results:
var isMessageStarred = message.isStarred() true
var isThreadStarred = thread.hasStarredMessages() true
GmailApp.unstarMessage(message).refreshMessage(message) would immediately give the following results:
var isMessageStarred = message.isStarred() false
var isThreadStarred = thread.hasStarredMessages() true
And GmailApp.unstarMessage(message).refreshMessage(message).refreshThread(thread) would immediately give the following results:
var isMessageStarred = message.isStarred() false
var isThreadStarred = thread.hasStarredMessages() false
I had this case. Actually what was happening was I was starring everything that hit the inbox with a filter. Once my script processed the message I unstarred it. But my script was also forwarding the message and that new sent message was being starred by the filter. So the thread still contained a starred message. Argh.
My solution was to create another filter to delete any messages from me.
Same problem here.
I am unable to unstar() emails which I starred manually in my browser/on my phone.
If I star an email via a rule or a script and then unstar it with a gapps script, unstarring works.
I also tried doing refresh right after unstarring - no difference:
message.unstar()
message.refresh()
I wrote a script which stars always the last message of a previously starred thread to keep my starred threads with the most recent replies at the top of my mailbox. The only problem is that it's not really working if I star an email manually at the beginning and not with a rule or another script.
The code going through my threads:
for(var z=0;z<messages.length;z++){
var message = messages[z]
var messageTime = message.getDate()
var messageTimeInCET = Utilities.formatDate(messageTime,'CET','yyyy-MM-dd HH:mm:ss')
//Logger.log('messageTime: '+ messageTime)
//Let's add star to the last email in the thread and remove stars from all previous emails
if(threadLastMessageDateInCET==messageTimeInCET){
Logger.log('Starred email from: '+ messageTimeInCET)
message.star()
}
else{
message.unstar()
message.refresh()
Logger.log('Unstarred email from: '+messageTimeInCET)
}
}

Counting emails in gmail with google apps script - for loop limits to 500 iterations

I am developing a small app to run inbox analytics on gmail. The gmail account I am using is a paid google apps account.
All is working well except that when counting for certain statistics with google apps script, if the count exceeds 500 the for loop stops and returns 500.
Is that a limitation of google apps script's looping? Is there anyway to increase the iterator limitation is so?
Below is the code
var DAYS_TO_SEARCH = 10; // look only in sent messages from last 7 days, otherwise script takes a while
var SINGLE_MESSAGE_ONLY = false; // exclude multi-message conversations where I sent the last message?
var NO_REPLY = 0;
function label_messages_without_response() {
var emailAddress = Session.getEffectiveUser().getEmail();
Logger.log(emailAddress);
var EMAIL_REGEX = /[a-zA-Z0-9\._\-]+#[a-zA-Z0-9\.\-]+\.[a-z\.A-Z]+/g;
var label = GmailApp.createLabel("AwaitingResponse");
var d = new Date();
d.setDate(d.getDate() - DAYS_TO_SEARCH);
var dateString = d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate();
threads = GmailApp.search("in:Sent after:" + dateString);
for (var i = 0; i < threads.length; i++)
{
var thread = threads[i];
if (!SINGLE_MESSAGE_ONLY || thread.getMessageCount() == 1)
{
var lastMessage = thread.getMessages()[thread.getMessageCount()-1];
lastMessageSender = lastMessage.getFrom().match(EMAIL_REGEX)[0];
if (lastMessageSender == emailAddress)
{
NO_REPLY ++;
Logger.log(NO_REPLY);
}
}
}
Logger.log("FINAL REPLY RATE:" + NO_REPLY);
}
Example output:
[16-11-26 19:59:35:548 PST] 497.0
[16-11-26 19:59:35:601 PST] 498.0
[16-11-26 19:59:35:652 PST] 499.0
[16-11-26 19:59:35:652 PST] FINAL REPLY RATE:499
If more then one day is calculated the FINAL REPLY RATES is always 499. However when I calculated just one day the result was 386, it seems anything exceeding 499 is not calculated.
Note * I'm sending between 300 and 700 emails a day from this account.
Much thanks for taking a look.
When working with a large ammount of emails you may be better off using the Gmail API instead. I have a script that fetches all emails between a start and end date and outputs the receive date, recipient, sender and title into a spreadsheet.
queriedMessages =
Gmail.Users.Messages.list(userInfo.mail, {
'q': queryString,
'pageToken': execProperties.nextPageId
});
That will get all individual messages, based on your queryString which is what you would enter into the Gmail search box, so you can use labels etc. (check advanced search for options). See the API reference here. After a quick look, you may want to also use Gmail.Users.Threads.list instead. Check on it here. It seems to work the same way Messages did (I needed separate messages as opposed to threads).
You will only get 1 page of threads/messages at once (which is 100), however you also get a next page token with the theads/messages, which you can then use as 'pageToken': to aim at the next page where in my code execProperties.nextPageId = queriedMessages.nextPageToken as obviously on the first go you will not have a nextPageId.
I would also recommend to set up a timeout procedure, as I found you will easily go over 6 minutes by getting large amounts of emails. What I do is have it check how long on average a loop takes to execute and if I have less time left (I use a 5 minute mark just in case) to do 1 more execution, I store everything in script properties, set up a trigger for 1 minute later and then terminate the script. That's why you see execProperties.nextPageId, because I might just have it from the previous execution.
The problem is the maximum threads you can get is 500, as a check you can try something like:
function threads (){
var threads = GmailApp.search("in:Sent");
Logger.log(threads.length);
}
The log will show 500 even if you have more.
The solution would be to do it in batches of 500:
threads = GmailApp.search("in:Sent after:" + dateString, start, max);
Note max in above cannot be greater than 500.

Removing "read" logic from gmail script

I am fairly new to google app scripts and hoped you could help me… I am sure this is an easy question.
I am running the following script to archive old emails. It currently only archives read emails, but I would like to have it archive messages with these labels regardless of whether they are read or unread. Any help would be appreciated!
function archiveInbox4() {
// Every thread in your Inbox that is read, older than fourteen days, and not labeled "delete me".
var threads = GmailApp.search('label: inbox older_than:14d label:"Calendar"|"wacuho-acuho-i"|"professional-organizations"');
for (var i = 0; i < threads.length; i++) {
threads[i].moveToArchive();
There's a way that you can mark emails read first then archive them as read emails.
Sample code is like:
function markArchivedAsRead() {
var threads = GmailApp.search('label:unread -label:inbox');
GmailApp.markThreadsRead(threads);
};
However this operation can only be applied to at most 100 threads. To fix this, you have to manually do a search for "is:unread" and mark all of them as read before running the script, so that it starts with a clean slate. The script can only process 100 threads per run, so if you give it more than 100 on the first run, that'll obviously bust it. See the reference link.
You can also have this code to get it done by google apps script:
function markArchivedAsRead() {
var threads = GmailApp.search('is:unread');
for (var i = 0; i < threads.length; i++) {
GmailApp.markThreadRead(threads[i]);
}
};