Does the following google script exactly delete the messages with certain label? - google-apps-script

My goal is to make sure that all incoming Gmail messages from test#test.com are immediately permanently deleted.
I have created a filter that gives new messages from this address the label "deleteforever". Next, I have made a Google script that completely deletes all messages with the label "deleteforever". To be certain that no other messages are deleted, I check an extra time whether the messages really are from test#test.com. In this way, when a thread contains messages from test#test.com and messages from another address, only the messages from test#test.com should be deleted.
I plan to run it every minute. I have 3 questions:
Does this algorithm always completely delete all new messages from test#test.com?
Does this algorithm never delete a message from another sender?
Will this not cost too much runtime, assuming that test#test.com does not send many emails? (I saw that there is a limit of 1 hour per day.)
function extractEmailAddress(header) {
return header.match(/[^#<\s]+#[^#\s>]+/)[0];
}
function deleteForever() {
var threads, msgs, label1, label2, sender,message,messageId;
label1="test#test.com";
label2="Test#test.com";
threads= GmailApp.getUserLabelByName("deleteforever").getThreads();
msgs = GmailApp.getMessagesForThreads(threads);
for (var i = 0 ; i < msgs.length; i++) {
for (var j = 0; j < msgs[i].length; j++) {
message=msgs[i][j];
sender = extractEmailAddress(message.getFrom());
if (sender==label1 || sender==label2){
messageId=message.getId();
Gmail.Users.Messages.remove("me", messageId);
}
}
}
}
UPDATE
Inspired by the comment of #TheMaster, the following strategy solves the potential runtime problem:
-modify the filter in Gmail so that messages from test#test.com skip the inbox.
-hide the "deleteforever" folder
Now the script can be run every 5 minutes or with even lower frequency.

Since you are open to muting the notifications as suggested by TheMaster and running the script/function on a less frequent basis, I suggest you improve it further by using Gmail API, specifically batchDelete to improve the performance.
Script:
function deleteForever() {
label_name = 'deleteforever';
sender1 = 'do-not-reply#stackoverflow.email';
sender2 = 'forms-receipts-noreply#google.com';
// get labelId of label_name
var labelId = Gmail.Users.Labels.list("me").labels.filter(label => label.name == label_name)[0].id;
// filter messages where it has labelId and from either sender1 or sender2
var messages = Gmail.Users.Messages.list("me", {
"labelIds": labelId,
"q": `{from: ${sender1} from: ${sender2}}`
}).messages;
// if messages is not empty
if(messages){
// get ids of the messages
var ids = messages.map(message => message.id);
// bulk delete the messages
Gmail.Users.Messages.batchDelete({"ids": ids}, "me");
}
}
This will delete the accumulated message IDs by bulk where it meets the conditions:
Sender is either sender1 or sender2
Has label label_name.
Note:
You will not need extractEmailAddress anymore.

Related

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.

How to read all emails in gmail using google apps script

I'm trying to read ALL email in my gmail account - inbox, sent, draft, trash, emails with labels, archive, etc. I could live without the junk but I want everything else.
(all examples below use try {} catch {} to avoid errors with empty labels etc.)
I've tried
for (var i=StartLabel; i<=EndLabel; i++)
{
var label = labels[i].getName();
// get all messages, then join them into a single dimension array
var messages = GmailApp.getMessagesForThreads(GmailApp.search("label:" + label))
.reduce(function(a, b) {return a.concat(b);});
CountByLabels += messages.length;
}
That gives me everything in the labels (I think) but not the other stuff.
I tried other things, to get the inbox (to combine with the above) or all of the emails
var messages = GmailApp.getMessagesForThreads(GmailApp.getInboxThreads()).reduce(function(a, b) {return a.concat(b);});
CountInbox += messages.length;
but I only get about 549 results (GMail shows 5,478). If I add in the results from getPriorityInboxThreads I get 1,829 results.
I tried
// get all messages, then join them into a single dimension array
var messages = GmailApp.getMessagesForThreads(GmailApp.search("(is:unread OR is:read) in:anywhere")).reduce(function(a, b) {return a.concat(b);});
CountByLabels += messages.length;
I get 598 results.
I tried different search terms in the code directly above, eg:
is:unread = 528 results
is:read = 1,037 results
is:read OR is:unread = 599 results
None of them gave the right number, or even close, and incidentally if I try those search terms directly in gmail I get a totally different, and much higher, result for each - several thousand, or 'many'.
I don't think this is related to How to use Google App Scripts to retrieve Gmail emails in a customised way? as the numbers returned are not round numbers (eg 500).
I'm assuming that I can use getSpamThreads, getStarredThreads, getTrashThreads, getDraftMessages to get the relevant folders but until I understand why I'm only getting some emails from the inbox I don't trust those to give me everything.
Can anyone help?
Try this:
function allEmailsInLabels() {
var allLabels,i,j,L,L2,msgCount,theCount,threads,thisLabel;
msgCount = 0;
theCount = 0;
allLabels = GmailApp.getUserLabels();
L = allLabels.length;
for (i = 0; i < L; i++) {
Logger.log("label: " + allLabels[i].getName());
thisLabel = allLabels[i];
threads = thisLabel.getThreads();
//Logger.log('threads: ' + threads);
L2 = threads.length;
for (j = 0; j < L2; j++) {
msgCount = threads[j].getMessageCount();
//Logger.log('thread message count: ' + threads[j].getMessageCount());
// You could do something with threads[j] here like
// threads[j].moveToTrash();
theCount = theCount + msgCount;
};
};
//Logger.log('theCount: ' + theCount);
};
It first gets all the labels, then the threads, then the message count in each thread, and keeps a running count. You'll also need to get the messages in the inbox, that code doesn't include them. This is the sample code from the documentation that shows the basic concept:
// Log the subject lines of your Inbox
var threads = GmailApp.getInboxThreads();
for (var i = 0; i < threads.length; i++) {
Logger.log(threads[i].getFirstMessageSubject());
}
I had the same question. Reading a little bit more in the reference in the Google Developers Website, I discovered, reading about the function moveToInbox, a Google sample that used the Search to get all e-mails that weren't in the Inbox (https://developers.google.com/apps-script/reference/gmail/gmail-thread#movetoinbox). I decided to combine this with the getInboxThreads and with these two, my code was shorter and found every e-mail that I had received (less spam and junk).
function getEmails() {
var generalThreads, inboxThreads;
inboxThreads = GmailApp.getInboxThreads();
generalThreads = GmailApp.search('-in:inbox');
}
Every single email that was in the folder "All mail" in the Gmail was in these two variables after this.
I don't know if this can help anyone, but surely helped me.
I know this is coming a bit delayed, but having had the same problem and looking at some of the solutions offered here, I wanted to offer up my own solution, which also uses the search function:
function getEmails() {
var allEmailThreads = GmailApp.search('label:all')
}
This actually filters for every email, regardless of the mailbox, and seems to me to be the simplest solution to the question.
This is not an answer to your problem (but is probably one of the reasons your total results returned don't agree with what you are seeing in gmail inbox) but highlights one of the problems I encountered when calling getPriorityInboxThreads() is that it ignores any thread that is not flagged as "important" in the primary inbox.
//returns 10 threads and 1st message for each thread
function getThreads(){
var ret = '';
var threads = GmailApp.getPriorityInboxThreads(0, 10);
for (var i = 0 ; i < threads.length; i++) {
var id = threads[i].getId();
var message = GmailApp.getMessageById(id);
ret += "subject: " + message.getSubject() +'\n';
Logger.log("subject: " + message.getSubject());
/*Edited this out as it doesn't return anything
//check for labels on this thread
var labels = threads[i].getLabels();
for (var j = 0; j < labels.length; j++) {
Logger.log(labels[j].getName());
} */
}
return ret;
}
"Important" is classed as a system flag and getPriorityInboxThreads() ignores any thread that is not flagged important....
I would like to select all threads in "Primary" inbox irrespective of being labelled as "important".
To test, simply change any thread in inbox to important or not etc.
After I published a video on how to get Gmail messages into a Google spreadsheet, I received a feedback from some viewers that they could only get a number of messages but others fail to be processed. Therefore, I did some research and found that the process of getting emails may fail and make the system unable to handle the huge amount of emails. This is mentioned in the Gmail API here:
https://developers.google.com/apps-script/reference/gmail/gmail-label#getthreads
The documentation suggests to use getThreads(start, max) where start and max are the limiting parameters.
You may view the video and download the full code from YouTube and GitHub:
https://youtu.be/gdgCVqtcIw4

Getting thread id of a mail sent through google scripts

Is it possible to get the thread id of a mail sent through MailApp.sendEmail().I want to tag the sent mail with a label just after it is sent.
MailApp.sendEmail("samplemail#gmail.com","Sellers Required for pilot",msg_to_bd);
//get thread id for this mail, say thread 1
thread1.addLabel(labll);
First, since you want to add labels to the thread you just sent, you must be using GmailApp. MailApp only allows you to send mail, not interact with the user's inbox.
As you've seen, GmailApp.sendEmail() does not return a message or thread ID. In this case, you can search for the thread you just sent, but you must account for when you've sent several messages to this person.
As long as you are not sending duplicate mails very quickly, you can rely on the fact that a call to GmailApp.search() will return threads in the same order as the web UI. So a search for 'from:me to:customer123#domain.net' might return many threads, but the first result will be the thread for the most recently sent message.
A toy example where we send a mail to a bunch of addresses listed in a tab called Recipients:
var recipients = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Recipients')
.getDataRange()
.getValues();
recipients.shift(); // Only if the Recipients sheet contained a header you need to remove
var d = new Date();
var dateText = d.toLocaleString(); // Pretty-printed timestamp
var newLabel = GmailApp.createLabel(dateText); // Label corresponding to when we ran this
for (var i = 0; i < recipients.length; i++) {
GmailApp.sendEmail(recipients[i], 'This is a test', 'Of the emergency broadcast system');
var sentThreads = GmailApp.search('from:me to:' + recipients[i]);
var mostRecentThread = sentThreads[0];
mostRecentThread.addLabel(newLabel);
}
Apps Script won't return the thread ID but what you can do is search for the subject in your mailbox after sending the email and apply the label to the first thread in the result.
var to="email#example.com", subject="email subject";
GmailApp.sendEmail(to,subject,msg_to_bd);
var threads = GmailApp.search("to:" + to + " in:sent subject:" + subject, 0, 1);
threads[0].addLabel(label);
Since all of the GmailApp.send* methods (at the time of writing) do not return a message or thread identifier, and since the GmailMessage object has no send* method, the safest thing to do seems like embedding a unique identifier into the message when it is sent. Then search for an email containing the unique identifier.
This code worked for me as an experiment. Note that I had to sleep() for a couple seconds in order for the search to succeed.
function tryit() {
var searchTerm = Utilities.getUuid();
GmailApp.sendEmail('address#somewhere.com', 'oh please',
'custom id: ' + searchTerm);
Utilities.sleep(2000);
var threadIds = GmailApp.search(searchTerm);
Logger.log(threadIds);
if (threadIds.length != 1) {
Browser.msgBox('Found too many threads with unique id '
+ searchTerm);
return;
}
return threadIds[0];
}
I suspect the reason we have to jump through hoops is that the API authors don't want to make sending email synchronous (maybe it can take too long), and hence they have no way to return an error or message id upon failure or success.
If you want to go completely crazy, you could send a message to yourself with the uuid, then spin in a while-sleep-search loop until you found the uuid and hence get a thread id, then reply to the thread with the full list of recipients. This guarantees that only your inbox suffers if things go wrong.
Im using this, it adds uuid hidden code into message, and I can find/label particular email with 100% precision:
var uid = Utilities.getUuid();
var uidText = '<span style="color:transparent; display:none !important; height:0; opacity:0; visibility:hidden; width:0">' + uid + '</span>';
GmailApp.sendEmail(parameters.email, subject, "", {"htmlBody":htmlEmail + uidText});
Utilities.sleep(1000);
GmailApp.search(uid)[0].addLabel(label)

Exceeded maximum execution time in Google App Script with Contacts API

I am creating an script responsible of generating a report containing info about a set of groups and its contacts in google app script.
If I select a few groups, everything goes OK but If I select several (20 or more), then the scripts launches the "Exceeded maximum execution time" error.
How could I reduce the script time execution?
Thanks!
My (reduced) script
var allContactsInfo = []
/II. For each group
for(var i = 0 ; i < numberOfGroups ; i++)
{
var selected = eventInfo.parameter["group"+i]
var contactInfo = []
//III. If it has been selected by the user
if(selected)
{
//IV. Get the contact group and contacts
var contactGroup = ContactsApp.getContactGroupById(eventInfo.parameter["group_"+i+"_id"])
var contacts = contactGroup.getContacts()
//IV. Iterate over each group contact
for (var j=0; j < contacts.length; j++)
{
var contact = contacts[j];
contactInfo.push(contact.getGivenName())
contactInfo.push(contact.getFamilyName())
var groups = contact.getContactGroups()
var groupsAsArray = []
for (var k = 0 ; k < groups.length; k++)
{
groupsAsArray.push(groups[k].getName())
}
contactInfo.push(groupsAsArray.sort().join())
//V. Add the contact into the array
allContactsInfo.push(contactInfo)
}
}
...
...
//VI. Fill the spreadsheet with the array built within the loop
sheet.getRange(1, 1, allContactsInfo.length, headers.length).setValues(allContactsInfo);
UPDATE
After some tests, I have found the bottleneck is produced returning user's groups (via contact.getContactGroups()).
Returning name, surnames, emails, addresses and phones works OK but If I include user's groups too, then timeout exception appears...
UPDATE 2
Here it is my working solution, hope it helps :)
https://github.com/antacerod/google-app-script-6-minutes-limitation
Finally I have found another SO post.
It shows a nice workaround related to checkbox setValue method implementation and its second parameter (Sets whether the CheckBox should be checked and optionally fires an event if the value changes as a result of this call)
In my case, it works like a charm!
Access the workaround

How to alternate email forwarding recipients?

I'm trying to find out if it is possible to set something up to forward emails to alternating recipients.
When I receive emails, a GMail filter tags some of them, with "elephant", for example.
There are multiple people who can handle "elephant" emails, so I want to forward each new email to one of them, cycling through the list of "elephant handlers". (Joe, Amy, and Tammy, say.) Their email addresses are available in a spreadsheet.
A
1 joe#example.com
2 amy#example.com
3 tammy#example.com
Pseudo code:
Get first unread email
Forward email to Joe
Get next unread email
Forward email to Amy
Get next unread email
Forward email to Tammy
Go to start
How can I accomplish this in Google Apps Script, so that it handles all new emails as they arrive?
It's possible to do this in a number of ways. In the world of telephony, the behavior is called a Hunt Group. Here's one suggestion that borrows that concept to get you started.
Assumptions:
You're using Gmail.
The script will belong to the same account holder as the Gmail account.
Approach:
You will set up a filter in Gmail to identify incoming messages that meet your criteria - the "elephant" emails.
In this filter, you will apply a label - "elephant" - to new emails that will be used by the script to identify "work to do".
A spreadsheet will be used to contain a script that will scan for unread messages related to the "elephant" Label and forward them.
The script will be set to Trigger on a timer event at an interval that suits you.
The spreadsheet will contain a list of (properly formatted) destination email addresses; Joe, Amy, Tammy. The script will read these and use them in order.
Once an email is processed, it will be marked as "read". You could optionally un-label, relabel, archive, or trash them.
The Script
This script keeps track of which recipient will get the next forwarded message by using ScriptProperties. If you expect very large numbers of messages, you'll need to enhance it to support getting messages in batches.
Remember to change labelName appropriately.
The script does some error checking, but doesn't validate email addresses - it's possible that messages may end up failing the forwarding operation because of that. Caveat Emptor.
This is also available as a gist.
/**
* Retrieves a given user label by name and forwards unread messages
* associated with that that label to a member of the Hunt Group.
*/
function huntGroupForward() {
// get the label for given name
var labelName = "elephant"
var label = GmailApp.getUserLabelByName(labelName);
if (label == null) throw new Error("No messages for label "+labelName);
// get count of all threads in the given label
var threadCount = label.getUnreadCount();
if (threadCount == 0) return; // quick exit if nothing to do.
var threads = label.getThreads();
var messages = [];
for (var i in threads) {
if (threads[i].isUnread()) {
messages = messages.concat( threads[i].getMessages() );
}
}
for (var i = 0; i < messages.length; i++) {
if (messages[i].isUnread()) {
messages[i].forward(nextHuntGroupMember());
messages[i].markRead();
}
}
};
/*
* Global object to store working copy of the Hunt Group
*/
var huntGroup = { next : 0, members : [] };
/*
* Get the email address of the next Hunt Group Member
* to forward a message to.
*/
function nextHuntGroupMember() {
if (huntGroup.members.length == 0) {
// Load members
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for (var i = 0; i < data.length; i++) {
huntGroup.members.push(data[i][0])
}
// Make sure we have members
if (huntGroup.members.length == 0) {
throw new Error("Found no email addresses");
}
}
// Retrieve next index. Properties are always stored as strings, so
// we need to parse the retrieved value to use it as a Number.
var next = parseInt(ScriptProperties.getProperty("nextHuntGroupMember"));
if (next != null) {
huntGroup.next = next;
}
else {
next = 0;
}
// get next member to be used
var nextMember = huntGroup.members[next];
// ... then move on to new next (increment modulo list length)
next = ++next % huntGroup.members.length;
// store the new next value
ScriptProperties.setProperty("nextHuntGroupMember", next);
return nextMember;
}
Trigger
Once you're happy with the script, set it up to run periodically. This is how you'd set it to run every hour: