Read all Emails with flag not set as "Mark Complete" only using EWS - exchangewebservices

I'm trying to read all the unread emails which are not marked as "Mark Complete" flag. I used below code to read email:
SearchFilter ReadFilter = new SearchFilter.SearchFilterCollection(LogicalOperator.And, new SearchFilter.Not(new SearchFilter.Exists(new ExtendedPropertyDefinition(0x1090, MapiPropertyType.Integer))));
But this is not returning emails whose flag is set by sender as "Today".
I'm using flag "Mark Complete" once my autosys job reads email and processes. So it is important to check if the email has "Mark Complete" before processing. But the above code is not fetching any email with flag set.

I would suggest you use the pidTagTaksStatus property instead https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/pidlidtaskstatus-canonical-property and then construct your filter like
ExtendedPropertyDefinition pidTagTaskStatus = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.Task, 0x8101, MapiPropertyType.Integer);
SearchFilter SetComplete = new SearchFilter.IsEqualTo(pidTagTaskStatus, 2);
SearchFilter sfCol = new SearchFilter.SearchFilterCollection(LogicalOperator.And)
{
new SearchFilter.Not(SetComplete)
};
So it should find anything that's flagged but not set to complete

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().

How to add condition in SSIS package to send mails only if attachments are available

I have created an SSIS package that includes one script task to add new attachment name and path in a variable after "|" and running it under foreach loop to include all attachment names and path in the variable value. then I am passing that variable as an attachment to send mail task. This package is running fine via batch file execution and sending one email containing multiple files as well.
Now, I want to schedule that batch file to run on every hour, and to accomplish that, I need to add logic in the package to send mail only if 2 attachments are available. It should not send any email if there are no or single attachment present. that way I want to remove manual job execution. Can you please help? I am new to SSIS development. Script task code as below:
if (Dts.Variables["User::FileNamesList"].Value.ToString() == "")
{
Dts.Variables["User::FileNamesList"].Value = Dts.Variables["User::InputPath"].Value.ToString() + Dts.Variables["User::Year"].Value.ToString() +"\\" + Dts.Variables["User::FileName"].Value.ToString();
}
else
{
Dts.Variables["User::FileNamesList"].Value = Dts.Variables["User::FileNamesList"].Value.ToString() + "|" + Dts.Variables["User::InputPath"].Value.ToString() + Dts.Variables["User::Year"].Value.ToString() + "\\" + Dts.Variables["User::FileName"].Value.ToString();
}
Anything involving mail is much better handled in script task.
Let me explain "better". You have a lot more control on To, Cc, Bcc, ReplyTo, etc. As well as being able to send a html formatted body.
This should run your entire application:
{
var fis = Directory.GetFiles(#"C:\folder"); //a 2nd param can be added for search pattern (ex. "*.xls")
//Check for number of files
if(fis.Length>1)
{
SendEmail("Where's it going", "Here are some files", "What you want in the body", fis);
//Archive
foreach (var f in fis)
File.Move(f, #"archive folder path" + new FileInfo(f).Name);
}
}
public static void SendEmail(string to, string subject, string body, string[] filePaths)
{
string from = "SendingMail#blah.com";
using (MailMessage mail = new MailMessage())
{
SmtpClient SmtpServer = new SmtpClient("YourSMTPClient");
mail.From = new MailAddress(from);
mail.To.Add(new MailAddress(to));
mail.Subject = subject;
mail.IsBodyHtml = true;
mail.Body = body;
foreach (var f in filePaths)
{
Attachment file;
file = new Attachment(f);
mail.Attachments.Add(file);
}
SmtpServer.Port = 9999; //Your SMTP port
SmtpServer.Credentials = new NetworkCredential(from, pword);
SmtpServer.EnableSsl = true;
SmtpServer.Send(mail);
}
}
This was resolved on same day. I was able add variable in my script task to count number of files (basically it will increase by 1 on each loop execution), then I added a simple condition into arrow connecting to mail send task to first check if the variable value is matching with expected values (created a variable in SSIS and assigned max expected file count), so it now it will send mail only if 2 files are available in folder. I have added a block only just to reset the variables and that also has second arrow attached from foreach loop , to run if the condition is not matching. The tool is now sending mail only for year which will have both files present or skip for that loop. I was able to schedule that as well.
Arrow condition true: before send mail task:
condition in arrow for script task to resent variable 2:
Counter:

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

Newly created Google Task omits the supplied "TaskLink" property

I am trying to make a small Google Script that would automatically add Google Tasks to the "My List" TaskList after searching my GMail emails.
Everything goes fine except for adding a link to the email from which the Task is generated from. Trying to follow the API documentation doesn't really help.
This is the code for the actual task generator function:
function addTask(taskListId, myTitle, myEmailLink) {
var task = Tasks.newTask(); // effectively same as "= {}".
task.title = myTitle
task.notes = 'blank';
task.links = [{}]
task.links[0].description = 'Link to corresponding email';
task.links[0].type = 'email';
task.links[0].link = 'myEmailLink';
task = Tasks.Tasks.insert(task, taskListId);
}
Any ideas why the task I receive back has no links?
As others have noted, according to the Google Tasks API Documentation the links collection is unfortunately read-only.
As a potential work around, it appears you can add links to the notes section of a task, and the links are then directly clickable from the tasks pane in GMail.
Picture: Task with clickable link
Your function can be modified to put the link in the notes section as follows:
function addTask(taskListId, myTitle, myEmailLink) {
var task = Tasks.newTask(); // effectively same as "= {}".
task.title = myTitle
task.notes = 'link: ' + myEmailLink;
task = Tasks.Tasks.insert(task, taskListId);
}
Combining this with the getPermalink() function on the GmailApp threads object allows for grabbing a deep link to the email you are looking for.
Picture: Task with permalink to email
I'm working on a set of scripts that do some of the things you're talking about in addition to a few other things: https://github.com/tedsteinmann/gmailAutoUpdate
In my solution I have a function that grabs the GMail threads with a particular label (in my case #Task) and then creates a task setting the subject to thread.getFirstMessageSubject() and the notes to thread.getPermalink()
The entire function looks like this:
function processPending_() {
var label_pending = GmailApp.getUserLabelByName(LABEL_PENDING);
var label_done = GmailApp.getUserLabelByName(LABEL_DONE);
// The threads currently assigned to the 'pending' label
var threads = label_pending.getThreads();
// Process each one in turn, assuming there's only a single
// message in each thread
for (var t in threads) {
var thread = threads[t];
// Grab the task data
var taskTitle = thread.getFirstMessageSubject();
var taskNote = 'Email: ' + thread.getPermalink();
// Insert the task
addTask_(taskTitle, taskNote, getTasklistId_(TASKLIST));
// Set to 'done' by exchanging labels
thread.removeLabel(label_pending);
thread.addLabel(label_done);
}
// Increment the processed tasks count
Logger.log('Processed %s tasks', threads.length);
}
Per the Google Tasks API Documentation:
links[] list
Collection of links. This collection is read-only.
You cannot set these links by modifying a Task resource, i.e your code
task.links = [{}]
task.links[0].description = 'Link to corresponding email';
task.links[0].type = 'email';
task.links[0].link = 'myEmailLink';
is simply ignored by the server.
TaskLinks are, to my knowledge, unusable and non-configurable outside of the Googleplex. They may as well not exist to API users.
The only way I've been able to generate a Task that has one is by using the Gmail UI and selecting "Add to Tasks". The resulting task then includes this snippet in the last line of the Task item:

Getting error when try to create meeting on exchange calendar through exchange api

I am using EWS Managed exchange api in c#
I am getting below error when i am try to create meeting on default calendar (Calendar).
Cannot create a calendar item in a non-calendar folder.
here is my code :
Appointment appointment = new Appointment(service);
appointment.Subject = "Test meeting";
appointment.Body = "Test Connection meeting";
appointment.Start = meetingStartTime;
appointment.End = meetingEndTime;
appointment.Sensitivity = Sensitivity.Normal;
appointment.Save(calendarId, SendInvitationsMode.SendToNone);
Note : I am fetching the calendar by calendar name.
As below
SearchFilter sfSearchFilter = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, "Calendar");
Please revert as soon as possible.
Most certainly the error is where you fetch the SearchResult.
A workaround would be to just call
appointment.Save(WellKnownFolderName.Calendar, SendInvitationsMode.SendToNone);
This calls for the default calendar folder without the need to search it first.