Getting thread id of a mail sent through google scripts - google-apps-script

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)

Related

Apps Script Gmail createDraftReplyAll wrong recipient: Puts me as recipient instead of other party

I have a working script for creating a new Gmail draft but when I use createDraftReplyAll in an existing thread, it puts the sender of the last email of the thread as the recipient of the draft.
So if I sent the last email, I become the recipient of the reply, and the other party is Cc'd. And if the other person wrote the last email, they are the recipient but I get CC'd. This is not normal behavior compared with manually creating a draft in Gmail (i.e. from clicking Reply To All), in which case the recipient will always be the other party.
Shown here (result of running the script below):
And normal behavior from clicking Reply All in Gmail:
This is the function I use to create the draft. The thread has already been found via the Subject and original message date:
function createDraftReplyInThread(emailThread, htmlTemplateFileName) {
Logger.log(">> IN createDraftReplyInThread");
Logger.log("htmlTemplateFileName: " + htmlTemplateFileName);
if (emailThread) {
let emailTo = emailThread.to;
Logger.log('Thread TO: ' + emailTo);
let threadMessages = emailThread.getMessages();
let messageCount = threadMessages.length;
let lastMessage = threadMessages[messageCount - 1];
let htmlTemplate = HtmlService.createTemplateFromFile(htmlTemplateFileName);
let htmlBodyContent = htmlTemplate.evaluate().getContent();
var myReplyID = lastMessage.createDraftReplyAll(
"Plain text for draft reply (not used)",
{
'htmlBody': htmlBodyContent
}).getId();
Logger.log(`Draft created in thread; myReplyID: ${myReplyID}`); // just to show it's executed okay
return;
}
return null;
}
It's probably best to ignore this, but I did try to use the limited existing update methods to copy the content, change the recipient and reinstate the the content in a new draft (pure guess work). Noting that draft.update() does work to change the recipient but it also requires a Message argument which I failed to get to work. Possible?
// based off stackoverflow: https://stackoverflow.com/questions/54745999/modify-only-the-subject-and-send-the-draft
function _sandbox_draftModify() {
var draft = GmailApp.getDrafts()[0];
var draftAsMessage = draft.getMessage();
var recipients = draftAsMessage.getTo();
var subject = draftAsMessage.getSubject();
var origRaw = draftAsMessage.getRawContent();
var newRaw = origRaw.replace("new draft", "old draft");
var newRawB64 = Utilities.base64EncodeWebSafe(newRaw, Utilities.Charset.UTF_8);
var emailTo = 'recipientsemail#gmail.com';
Logger.log(`recipients: ${recipients}`);
Logger.log(`subject: ${subject}`);
Logger.log(`newRaw: ${newRaw}`);
draft.update(emailTo, subject, { message: { raw: newRawB64 } });
var draftNew = GmailApp.getDrafts()[0];
userId = 'me';
Gmail.Users.Drafts.update({ message: { raw: newRawB64 } }, userId, draftNew.getId());
Logger.log("Done");
}
In the end all I need is to be able to create a draft email to an existing thread (which I can do already) but with the correct recipient(s), however that may be achieved.
This seems to be the expected behavior for the createDraftReplyAll method.
If you check the documentation page here:
createDraftReplyAll(body)
Creates a draft message replying to the sender of the last message in this thread, using the reply-to address and all recipients of this message. The size of the email (including headers) is quota limited.
However, you can file a feature request on Issue Tracker if you would like to have a functionality which replicates the UI behavior. The form can be found at this link and make sure to fill in all the necessary information.
Reference
Apps Script GmailThread Class - createDraftReplyAll().

Split Gmail thread and label by date in a google script

Hy,
I have a server sending me several log mails by day and I want to automaticly label this mails.
I can't touch the server configuration to adapt the mail subject, so the work must be done by "receiver".
The Subject is still same so gmail merge them in a thread of 100, but I want to split them by date. So One Date, one thread. In addition, I want label them whith a nested label: "Server1" -> "Date"
I've only found a way to add label to the thread in globality and no way to split them.
Is it even possible?
After a new look on my issue, perhaps add the date at the message subject can split threads.
Like:
function AddLogSubjectADate() {
var threads = GmailApp.search('from:sender#server.com has:nouserlabels');
threads.forEach(function(messages){
messages.getMessages().forEach(function(msg){
var date = msg.getDate();
var date_of_mail = Utilities.formatDate(date, "GMT+1", "yyyy/MM/dd")
var subj = msg.getSubject()
var newsubj = subj + date_of_mail
//A way to modify subject
});
});
}
But I didn't find a way to change the subject.
Post Scriptum
I don't think it's relevant, but here is my previous work. but it add label to the thread. Like I said I haven't find a way to split threads.
function AddLogLabelbyDate() {
var today = new Date();
var tomorrow = new Date();
var yesterday = new Date();
tomorrow.setDate(today.getDate()+1);
yesterday.setDate(today.getDate()-1);
var date_today = Utilities.formatDate(today, "GMT+1", "yyyy/MM/dd")
var date_tomorrow = Utilities.formatDate(tomorrow, "GMT+1", "yyyy/MM/dd")
var date_yesterday = Utilities.formatDate(yesterday, "GMT+1", "yyyy/MM/dd")
var threads = GmailApp.search('from:sender#server.com has:nouserlabels before:'+ date_tomorrow +' after:'+ date_yesterday +'');
label.addToThreads(threads);
}
Per the API documentation, Gmail follows some rules about thread grouping:
In order to be part of a thread, a message or draft must meet the following criteria:1. The requested threadId must be specified on the Message or Draft.Message you supply with your request.2. The References and In-Reply-To headers must be set in compliance with the RFC 2822 standard.3. The Subject headers must match.
So, you can prevent the automatic grouping into a given conversation thread by modifying any of those 3 parameters.
Alternately, you can apply per-message conversation labels, though this will not really help you if you use "Conversation View" UI.
Both of these methods require the use of the Gmail REST API, for which Apps Script provides an "advanced service" client library. The native GmailApp does not provide a method for per-message thread alteration, or for manipulating messages in the manner needed.
Thread Separation
If you wanted to disable the conversation grouping, in theory you could do this:
Message#get to obtain a full message representation
Modify one of the properties Gmail uses to perform thread grouping
Message#insert or import to create the new message on the server
Message#delete to remove the original
Message#get to get the inserted message metadata, after Gmail has given it a threadId.
Get the remaining messages that should share that new threadId, modify them appropriately, and insert.
Repeat.
I haven't tested that approach, hence my "in theory" comment.
Per-message labeling
The relevant API methods include Gmail.User.Labels.list, Gmail.User.Messages.list, Gmail.User.Messages.modify, and Gmail.User.Messages.batchModify. You'll probably want to use the list and messages.batchModify methods, since you seem to have a large number of messages for which you'd like to make alterations. Note, there are non-trivial rate limits in place, so working in small batches is liable to be most resource-efficient.
This is likely to be the simplest method to implement, since you don't have to actually create or delete messages - just search for messages that should have a given label, add (or create and add) it to them, and remove any non-desired labels. To start you off, here are some minimal examples that show how to work with the Gmail REST API. I expect you will need to refer to the API documentation when you use this information to construct your actual script.
An example Labels#list:
function getLabelsWithName(labelName) {
const search = Gmail.Users.Labels.list("me");
if (!search.labels || !search.labels.length)
return [];
const matches = search.labels.filter(function (label) {
// Return true to include the label, false to omit it.
return label.name === labelName;
});
return matches;
}
An example Messages#list:
function getPartialMessagesWithLabel(labelResource) {
const options = {
labelIds: [ labelResource.id ],
fields: "nextPageToken,messages(id,threadId,labelIds,internalDate)"
};
const results = [];
// Messages#list is paginated, so we must page through them to obtain all results.
do {
var search = Gmail.Users.Messages.list("me", options);
options.pageToken = search.nextPageToken;
if (search.messages && search.messages.length)
Array.prototype.push.apply(results, search.messages);
} while (options.pageToken);
return results;
}
An example Messages#batchModify:
function batchAddLabels(messageArray, labels) {
if (!messageArray || !messageArray.length || !messageArray[0].id)
throw new Error("Missing array of messages to update");
if (!labels || !labels.length || !labels[0].id)
throw new Error("Missing array of label resources to add to the given messages");
const requestMetaData = {
"addLabelIds": labels.map(function (label) { return label.id; }),
"ids": messageArray.map(function (msg) { return msg.id; }) // max 1000 per request!
};
Gmail.Users.Messages.batchModify(requestMetaData, "me");
}
Additional Resources:
Message Searches
"fields" parameter

Receiving duplicate emails with sendEmail()

I tried both MailApp.sendEmail() and GmailApp.sendEmail() to send an email confirmation from onFormSubmit and ending up with multiple duplicate emails (as many as 6). The code looks like this:
function sendEmailConf_(ss, email, session) {
Logger.log("sendEmailConf_ email: %s for session: %s", email, session);
var formUrl = ss.getFormUrl(); // Use form attached to sheet
var form = FormApp.openByUrl(formUrl);
var formResponses = form.getResponses();
Logger.log("Count of form responses: %s", formResponses.length);
for (var i = 0; i < formResponses.length; i++) {
if (formResponses[i].getRespondentEmail() == email) {
Logger.log("Sending email to: %s for session: %s", email, session[0]);
GmailApp.sendEmail(
email,
'Confirmation for registration of: ' + session[0] + ', ' + getSessionSchedStr(session),
('Thanks for registering!\n\n' + getResponseAsText(formResponses[i]) + '\n\n' +
'You may change your response using this URL: ' + formResponses[i].getEditResponseUrl())
);
}
}
}
Using script transcript and log statements, I confirmed that sendEmail() is getting called only once and that the email is a string with single email address in it. The emails I receive have exactly the same body and are received at the same time and they all have the same from and to addresses (both mine, since I am testing it). Anybody has a clue on what is going wrong here?
Edit: Just observed that the duplicate count is increasing by one every time it is run. I just tried it again and got 7 fresh emails, all exact duplicates (and different from prior 6). I am clueless on what could be causing such a behavior.
Open the script editor and choose Resources -> Current Project Triggers. Make sure you only have a single trigger associated with the script.
If you have shared the script with multiple users, you'll have to repeat this from the account of every user who may have authorized the script.

Need random unique code using Google Apps Script

I'd created a Google form for collecting data from users. In this form I'm going to ask the site name and want to replace it with Unique code.
https://docs.google.com/forms/d/1JTStfAZGFBAFjVxgH0aZlMUroVsBijC0sfOvuXvqce8/viewform?usp=send_form#start=invite
I'd written the codes with google apps script for this which is given below but every time it generate new unique code and also replace previous one also.
function SendConfirmationMail(e) {
try {
var ss, bcc, sendername, subject, columns, username;
var message, value, textbody, usermail, track, code;
// This is your email address and you will be in the BCC
bcc = Session.getActiveUser().getEmail();
// This will show up as the sender's name
sendername = "Support Team";
// This is the submitter's Name
username = e.values[1];
// This is the submitter's email address
usermail = e.values[2];
// Custom subject for Google Docs emails
subject = "Support Ticket";
// Random Code for Google Docs emails
ss = SpreadsheetApp.getActiveSheet();
track = new Array();
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 10;
var lastRow = ss.getLastRow()-1;
var randomstring = '';
for (var i=0; i<string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum+1);
}
track.push(randomstring);
code = ss.getRange(2, 5, lastRow, 1).setValue(track);
// This is the body of the auto-reply
message = "Hi <b>" +username+ " !!!</b> <br><br> Your message has been successfully received.<br>";
GmailApp.sendEmail(usermail, subject, messag,
{bcc: bcc, name: sendername, htmlBody: message});
} catch (e) {
Logger.log(e.toString());
}
}
But I want to generate unique code for every submission. Please update me.
I've struggled with this sort of thing before, and the complexity depends on the security it needs (as far as I can tell, this is all internal, so not much).
I'm not sure I understand why you'd put your unique code as an editable text box (I'm assuming site name is just a holdover and you would eventually either make it a non-editable div or not show it at all).
Either way, one method is to keep an array of all your previous unique codes in properties, although if this is used a lot, then that could be too much (look at the quotas, which Wolfram Alpha says is about 500 words conservatively).
So, maybe you could have a few scriptDBs (or even a spreadsheet, although it's slower) that you combine to get a final array of all your previous codes. Your randomstring algorithm could be fine (as long as you check to make sure it doesn't exist in the overarching scriptDB array).
Look at Generate random string/characters in JavaScript for other random string ideas, but just make sure (again, if this gets very popular) that there are enough new possibilities.
You don't want this hanging while looking for a new code, so I would also add a counter and do something else if it takes more than 100 iterations (if this is all internal, it might not be a bad idea to have a 5-10 character code just to keep the chances of it not finding a new string quickly much lower).

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: