Google Apps substring from null - google-apps-script

I put my first Google Apps Script together to write emails to a spreadsheet. It works fine, except of the substring method I would like to use to shorten the message body. The script engine returns "cannot call substring from null […]". I found this thread (google apps script TypeError: Cannot call method "substring" of undefined) but the solution didn't help – or I didn't understand it which is about as likely.
Here's my script.
function labelToSpreadsheet() {
var threads = GmailApp.getUserLabelByName('newaddress').getThreads();
var spreadsheet = SpreadsheetApp.openById("onehellofanID")
var sheet = spreadsheet.getSheets()[0];
Logger.log (spreadsheet.getName());
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
for (var j = 0; j < messages.length; j++) {
var shortendContent = messages[j].getPlainBody().substring(0, 500);
sheet.appendRow([messages[j].getSubject(), messages[j].getFrom(), messages[j].getReplyTo(), messages[j].getDate(), shortendContent]);
}
}
};

This was driving me nuts, as there was no reason your code wouldn't work, and I was getting the same error. I narrowed it down to the 'getPlainBody()' was returning 'null', but couldn't figure out why this was the case even when I used GMails own example.
I was almost going to call it a bug when I realized that what was happening was that some messages were returning nothing in the body. Specifically, some companies send out newsletters that aren't text content at all, but in fact are images with their content inside (Which was the case with the first message in the test label I had, thus driving me bonkers).
So, the issue here is that the label you're running this under has some messages where the content is only an image, no text whatsoever (Or potentially, is just completely blank), thus 'getPlainBody()' returns 'There's nothing there'(Null) and you can't get a substring of nothing.
A simple 'if' statement actually handles this error really well, as you can then tell the script to write to the sheet 'The content of this message was an image' (Or whatever you want).
This slightly modified version of your code works for me:
function labelToSpreadsheet() {
var threads = GmailApp.getUserLabelByName(LABELNAME).getThreads();
var spreadsheet = SpreadsheetApp.openById(SHEETID);
var sheet = spreadsheet.getSheets()[0];
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
for (var j = 0; j < messages.length; j++) {
if(messages[j].getPlainBody() == null){
var shortendContent = 'This message was an image';
}else{
var shortendContent = messages[j].getPlainBody().substring(0, 500);
};
sheet.appendRow([messages[j].getSubject(), messages[j].getFrom(), messages[j].getReplyTo(), messages[j].getDate(), shortendContent]);
}
}
};
I'm giving myself a gold star for this one, it was annoying to figure out.

Related

Most efficient way to traverse Gmail attachments and download to Google Drive?

Using Google Apps Script, is there a more efficient way to traverse my Gmail - picking out 'non-starred' emails that have a particular label assigned to them and then download the attachments to Google Drive?
My code works, but typically 'times out' after processing about 25 image attachments (using non-paid Gmail account)
The piece of code that does the work is as follows:
// Loop through the messages for each thread
for (var i = 0 ; i < messages.length; i++) {
for (var j = 0; j < messages[i].length; j++) {
var CurrentMsg = messages[i][j];
if (!CurrentMsg.isStarred()){
var att = CurrentMsg.getAttachments();
// If there were no attachments, create a 'dummy text file' to notify the user that there were no attachments for that email.
var MsgSubject = CurrentMsg.getSubject();
if (att.length == 0){
var file = folder.createFile(MsgSubject,'There were no attachments',MimeType.PLAIN_TEXT);
}
else{
for (var k = 0; k < att.length; k++){
var file = folder.createFile(att[k].copyBlob().getAs('image/jpeg').setName(MsgSubject));
}
}
CurrentMsg.star();
}
}
}
Any tips gratefully received!
If you want to look through certain emails only, like those that are not starred in your case, consider using the search() method. This will help you avoid looping over threads and messages you don't need.
If you need to bypass your maximum execution time, check out this answer and this article (also linked in the answer).
I would recommend limiting results via a query then using the foreach function to go through messages:
// limit the list of messages to iterate through
var query = 'has:attachment label:particular';
var results = Gmail.Users.Messages.list(userId, {q: query});
results.messages.forEach(function (m) {
var msg = GmailApp.getMessageById(m.id);
msg.getAttachments().forEach(function (a) {
var fileName = a.getName();
fileName = saveAttachmentToFolder(folder, a, fileName, msg.getDate(), input.tz);
});
});
The code snippet above is based on a Gmail add-on that I created, specifically for saving attachments to labeled folders in Drive: https://github.com/ellaqezi/archiveByLabel/blob/main/Code.gs#L24
In the label field, you can define nested directories to create in Drive e.g. foo/bar.
In the query field, you can copy the parameters as you would use them in Gmail's search bar.

Getting the body of individual emails from gmail to google sheets

I'm really new at using Google Apps Script, so if what I'm trying doesn't make sense, or just isn't possible please let me know.
Everyday I get several emails that look like the following:
Your Name: FirstName LastName
Phone Number: 555 867 5309
Email Address: FakeEmail#email.com
What do you need help with? Request someone makes.
I'm attempting to automatically send the body of these emails to a new line in a Google Sheet when they come in.
As of right now I have every email get the label "myLabel" when it comes in. I then run the following script, which is a slightly modified version of something I found here:
function myFunction() {
var ss = SpreadsheetApp.getActiveSheet();
var label = GmailApp.getUserLabelByName("MyLabel");
var threads = label.getThreads();
for (var i=0; i<threads.length; i++)
{
var messages = threads[i].getMessages();
for (var j=0; j<messages.length; j++)
{
var msg = messages[j].getBody();
ss.appendRow([msg])
}
threads[i].removeLabel(label);
}
}
I'm attempting to run this code with a timer trigger every 15 minutes. The issue I've run into is that every time the code runs it pulls from every email in the thread. I would like it to just pull from the emails that are new since the last time it ran. Any advice would be greatly appreciated.
Why not mark the messages as read when you finish processing them? Here is a sample from one of my scripts.
var pendingEmailLabel = "MyLabel";
var threads = GmailApp.getUserLabelByName(pendingEmailLabel).getThreads();
for (var t = 0; t < threads.length; ++t) {
var thread = threads[t];
var messages = thread.getMessages();
for (var m = 0; m < messages.length; ++m) {
var message = messages[m];
if (message.isUnread()) {
// INSERT YOUR CODE HERE THAT TAKES ACTION ON THE MESSAGE
message.markRead();
}
}
}
}

permanently delete only one gmail message from a thread using a google script

I want to permanently delete a Gmail message inside a thread already in the trash.
I merged a few scripts around there, so I can delay and track emails. It works by saving a draft, then the script copy the draft into a new email, send it at the specified time and send the original draft to trash. The problem is that once in a while, the drafts that are in the trash are sent again (i haven't been able to figure out why yet)...
As a workaround, I was using the following code that that was originally posted here: delete forever emails 1:
function cleanUp() {
var threads = GmailApp.search("in:trash is:draft");
Logger.log(threads.length);
for (var i = 0; i < threads.length; i++) {
Logger.log(threads[i].getId());
Gmail.Users.Message.remove('me',threads[i].getId());
}
}
This was working fine, until a while ago. If the draft was inside a thread with more than 1 message, only the draft was deleted... I got now an error on line 6 that says: "Cannot call method "remove" of undefined".
In this post: delete forever emails 2, it is suggested to replace line 6 by
Gmail.Users.Threads.remove('me',threads[i].getId());
This dosn't get any errors, but if the draft is in a thread with more than one message, the whole thread is deleted instead of only the draft...
So, is there a way to get only the draft erased?
I tried calling the message id of the draft inside the thread and use the original line 6:
function cleanUp2() {
var threads = GmailApp.search("in:trash is:draft");
Logger.log(threads.length);
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
Logger.log(messages.length);
for (var j = 0; j < messages.length; j++){
if (messages[j].isDraft()){
Logger.log('id msg: ' + messages[j].getId());
Gmail.Users.Message.remove('me',messages[j].getId());
}
}
}
}
But I got the same error, now on line 10...
I also tried using this function:
function deleteMessage(userId, messageId) {
var request = gapi.client.gmail.users.messages.delete({
'userId': userId,
'id': messageId
});
request.execute(
function(resp) { });
}
That you can find in the developers page of google: here. In the "try this API" section it works, but in my implementation i got an error on line 2 that says (translated from Spanish so i don't know if it will be exact): "a name (?) is missing behind (after?) operator "."" And if i copy the function in a separated tab, i can save it and the same error is showed...
Any help will be appreciated...
Regards,
i finally made it trough an http request:
function cleanUp2() {
var threads = GmailApp.search("in:trash is:draft");
Logger.log(threads.length);
var userId = 'xxxxx#gmail.com';
var options = {
'method' : 'delete',
'muteHttpExceptions': true
};
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
Logger.log(messages.length);
for (var j = 0; j < messages.length; j++){
if (messages[j].isDraft()){
Logger.log('id msg: ' + messages[j].getId());
var url = 'https://www.googleapis.com/gmail/v1/users/' + userId + '/messages/' + messages[j].getId();
var response = UrlFetchApp.fetch(url,options);
Logger.log(response);
}
}
}
}

getMessageById() slows down

I am working on a script that works with e-mails and it needs to fetch the timestamp, sender, receiver and subject for an e-mail. The Google script project has several functions in separate script files so I won't be listing everything here, but essentially the main function performs a query and passes it on to a function that fetches data:
queriedMessages = Gmail.Users.Messages.list(authUsr.mail, {'q':query, 'pageToken':pageToken});
dataOutput_double(sSheet, queriedMessages.messages, queriedMessages.messages.length);
So this will send an object to the function dataOutput_double and the size of the array (if I try to get the size of the array inside the function that outputs data I get an error so that is why this is passed here). The function that outputs the data looks like this:
function dataOutput_double(sSheet, messageInfo, aLenght) {
var sheet = sSheet.getSheets()[0],
message,
dataArray = new Array(),
row = 2;
var i, dateCheck = new Date;
dateCheck.setDate(dateCheck.getDate()-1);
for (i=aLenght-1; i>=0; i--) {
message = GmailApp.getMessageById(messageInfo[i].id);
if (message.getDate().getDate() == dateCheck.getDate()) {
sheet.insertRowBefore(2);
sheet.getRange(row, 1).setValue(message.getDate());
sheet.getRange(row, 2).setValue(message.getFrom());
sheet.getRange(row, 3).setValue(message.getTo());
sheet.getRange(row, 4).setValue(message.getSubject());
}
}
return;
};
Some of this code will get removed as there are leftovers from other types of handling this.
The problem as I noticed is that some messages take a long time to get with the getMessageById() method (~ 4 seconds to be exact) and when the script is intended to work with ~1500 mails every day this makes it drag on for quite a while forcing google to stop the script as it takes too long.
Any ideas of how to go around this issue or is this just something that I have to live with?
Here is something I whipped up:
function processEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var messages = Gmail.Users.Messages.list('me', {maxResults:200, q:"newer_than:1d AND label:INBOX NOT label:PROCESSED"}).messages,
headers,
headersFields = ["Date","From","To","Subject"],
outputValue=[],thisRowValue = [],
message
if(messages.length > 0){
for(var i in messages){
message = Gmail.Users.Messages.get('me', messages[i].id);
Gmail.Users.Messages.modify( {addLabelIds:["Label_4"]},'me',messages[i].id);
headers = message.payload.headers
for(var ii in headers){
if(headersFields.indexOf(headers[ii].name) != -1){
thisRowValue.push(headers[ii].value);
}
}
outputValue.push(thisRowValue)
thisRowValue = [];
}
var range = ss.getRange(ss.getLastRow()+1, ss.getLastColumn()+1, outputValue.length, outputValue[0].length);
range.setValues(outputValue);
}
}
NOTE: This is intended to run as a trigger. This will batch the trigger call in 200 messages. You will need to add the label PROCESSED to gmail. Also on the line:
Gmail.Users.Messages.modify( {addLabelIds:["Label_4"]},'me',messages[i].id);
it shows Label_4. In my gmail account "PROCESSED" is my 4th custom label.

Get Gmail categories

I used to have a working apps script that deleted old promotional emails in my Gmail account.
It used the code
var label = GmailApp.getUserLabelByName("Promotions");
to get the label and then it iterated through label.getThreads() to decide whether each thread was old enough to delete.
However, Google have now changed Gmail so the auto categorisations are now under the Categories section in the UI, rather than within the list of labels so the above now returns null.
How can I fix my code to retrieve the Promotions category? I have tried Categories/Promotions too but it also comes up null.
Gmail categories can easily be searched.
Here is a small code that looks for every promotion mail. The result is an array of threads, you can add Label to each of them so that your old script will be happy again ;-)
var threads = GmailApp.search('category:promotions');// check the category Gmail added to the thread
documentation here
Here is a script I use to delete old promotional emails as well as some other categories and custom labels.
function auto_delete_email(){
delete_Label ("Cameras",30);
delete_Label ("Travel",365);
delete_Category ("Social",90);
delete_Category ("Finance",365*3);
delete_Category ("Forums",90);
delete_Category ("Promos",365*3);
}
function delete_Label(mailLabel,delayDays) {
var label = GmailApp.getUserLabelByName(mailLabel);
if (!label) {return false;}
var maxDate = new Date();
maxDate.setDate(maxDate.getDate()-delayDays);
var threads = label.getThreads();
for (var i = 0; i < threads.length; i++) {
if (threads[i].getLastMessageDate()<maxDate){
threads[i].moveToTrash();
}
}
function delete_Category(mailCategory,delayDays) {
var maxDate = new Date();
maxDate.setDate(maxDate.getDate()-delayDays);
var threads = GmailApp.search('category:' + mailCategory);
for (var i = 0; i < threads.length; i++) {
if (threads[i].getLastMessageDate()<maxDate){
threads[i].moveToTrash();
}
}
}
You can also expand the search criteria to use (d, m, y) like "older_than:1y" which mimics the search box functionality in the Gmail app. This may be helpful as I've had significant issues getting the date comparison to work smoothly.
For example:
function delete_old_Category() {
var maxDate = new Date();
maxDate.setDate(maxDate.getDate()- 180);
var threads = GmailApp.search("category:promos older_than:6m",0, 100);
for (var i = 0; i < threads.length; i++) {
threads[i].moveToTrash(); {
}
}
}