EWS save sent item in WellKnownFolderName.SentItems - c#

I'm trying to send an EmailMessage using EWS with user A and save the sent item in the SentItems folder of user B. Basically it works. The only problem I encounter, the item is saved as a draft and not as a sent item.
The code:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2, timeZone)
{
Url = new Uri(uri),
Credentials = new WebCredentials(new NetworkCredential(user, password, domain)),
UseDefaultCredentials = false,
};
EmailMessage message = new EmailMessage(service)
{
Subject = subject,
Body = new MessageBody(BodyType.HTML, fullBody)
};
message.From = email;
message.ToRecipients.Add(email);
FolderId folderId = new FolderId(WellKnownFolderName.SentItems, email);
What I tried:
// Simply sends the message
message.Send();
// Sends the item but is it not saved in the sentItems of email-account
FolderId folderId = new FolderId(WellKnownFolderName.SentItems, email);
message.SendAndSaveCopy(folderId);
// Sends the item, saves the item in the right folder, but it is saved as a draft, not as a sent item
FolderId folderId = new FolderId(WellKnownFolderName.SentItems, email);
message.Send();
message.Move(folderId);
What am I missing or doing wrong?
This guy tells to simply save and then save usind the folderId, but in such a scenario I get the following error:
This operation can't be performed because this service object already has an ID. To update this service object, use the Update() method instead.

After some tests I figured out that correct procedure is the following one:
FolderId folderId = new FolderId(WellKnownFolderName.SentItems, email);
message.SendAndSaveCopy(folderId);
However, there must be a bug because the item is always saved in the sentItems folder of the user used to authenticate the ExchangeService ignoring the folderId passed as argument.
The solution (workaround) is to impersonate the user as Microsoft Documentation explains. Here the impersionation code:
service.AutodiscoverUrl(email);
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, email);
It works...I wonder if somebody else was able to arrange it in a proper way.

Related

C# Exchange web service - send and immediately retrieve sent message

The system I'm working has a requirement where the MimeContent of a just-sent email is stored in a local database. My understanding of how Exchange works is that it will create the MimeContent on the server and I cannot access it unless I query the service for that just-sent message.
So, the steps I take are:
-- Send the email and get it's Id
message.SendAndSaveCopy();
return message.Id.UniqueId;
-- Use the new id to get the just-sent EmailMessage
ExchangeService exchangeService = ExchangeService;
var properties = new List<PropertyDefinitionBase>
{
ItemSchema.MimeContent
};
EmailMessage message = EmailMessage.Bind(exchangeService, new ItemId(messageId), new PropertySet(BasePropertySet.IdOnly, properties));
When this code runs without interruption, it works. The Id returned is still valid (message is in the outbox folder perhaps) and I get the result. However, if I slow it down for even a second, the Id is no longer valid (I guess it's now moved into the sent folder).
I cannot leave it like this as there is no guarantee I will get back to the server in time to retrieve the message with that Id.
If there is a solution that involves me not having to query the service for the message again, that'd be swell. However, if not, is there a way I can use the Id and ChangeKey to get the just-sent email?
Eventually answered my own question. I did this by adding a custom property onto each email that I control. I then use that same value to search for the email. I had some issues with the email moving between folders inbetween the queries but it's sorted now.
Define this somewhere, I have it within my EmailProvider class:
private readonly PropertyDefinitionBase _TempEmailId = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.PublicStrings, "TempEmailId", MapiPropertyType.String);
Then add it just before you send the email:
string messageId = null;
if (getTempId)
{
messageId = Guid.NewGuid().ToString();
message.SetExtendedProperty((ExtendedPropertyDefinition) _TempEmailId, messageId);
}
message.SendAndSaveCopy();
return messageId;
Finally, use the messageId to get the MimeContent (or any other properties you desire):
/// <summary>
/// Get the mime content for an email just sent
/// </summary>
/// <param name="messageId"></param>
/// <returns></returns>
public byte[] GetJustSentMimeContent(string messageId)
{
ExchangeService exchangeService = ExchangeService;
// Use the following search filter to get mail with TempEmailId set to what we just got
var searchCriteria = new SearchFilter.IsEqualTo(_TempEmailId, messageId);
var itemView = new ItemView(1) {PropertySet = new PropertySet(BasePropertySet.IdOnly)};
byte[] GetMimeContent(WellKnownFolderName folder)
{
var items = exchangeService.FindItems(folder, searchCriteria, itemView);
if (items.TotalCount > 0)
{
// if it's still in there, try and load the properties for it
exchangeService.LoadPropertiesForItems(items,
new PropertySet(BasePropertySet.IdOnly,
ItemSchema.MimeContent));
var emailMessage = (EmailMessage) items.First();
// if the content has been loaded, return it
if (emailMessage.MimeContent != null)
return emailMessage.MimeContent.Content;
}
return null;
}
// check the drafts folder to see if it's still in there
var mimeContent = GetMimeContent(WellKnownFolderName.Drafts);
if (mimeContent != null)
return mimeContent;
// if at this point, either:
// - email was moved to SentItems before the first search was done
// - or email was found in Drafts but then moved to SentItems but before the MimeContent could be loaded. Need to restart the process and find the email in SentItems instead
// should be in here (sentItems) now, try and load the properties for it
return GetMimeContent(WellKnownFolderName.SentItems);
}

Fetch emails received on certain day from non-Well-known folder in shared inbox, Exchange 2016 EWS in C#

I'm trying to fetch all messages from a user-specified date on an Exchange 2016 server using the EWS managed API in C#.
I authenticate with:
public static void Login(string username, string password)
{
service.UseDefaultCredentials = false;
service.Credentials = new WebCredentials(username, password);
service.AutodiscoverUrl(username, RedirectionUrlValidationCallback);
}
Then select the appropriate inbox with
sharedMailbox = new Mailbox(Properties.Settings.Default.Inbox);
the SMTP address is stored in Settings.settings .
I then find the desired folder using the following (from this thread):
targetFolderId = new FolderId(WellKnownFolderName.Inbox, sharedMailbox);
// set folder view
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
view.PropertySet.Add(FolderSchema.DisplayName);
view.Traversal = FolderTraversal.Deep;
folderResults = service.FindFolders(WellKnownFolderName.Inbox, view);
foreach(Folder f in folderResults)
{
if(f.DisplayName == "Invoices")
{
targetFolderId = f.Id;
//tried showing a message box here
}
}
And use the following (filter code from here and retrieve details from Exchange server code from here) to get the messages I want:
public static void FetchUnreadMessages(DateTime searchDate)
{
SearchFilter greaterthanfilter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeReceived, searchDate);
SearchFilter lessthanfilter = new SearchFilter.IsLessThan(ItemSchema.DateTimeReceived, searchDate.AddDays(1));
SearchFilter dayFilter = new SearchFilter.SearchFilterCollection(LogicalOperator.And, greaterthanfilter, lessthanfilter);
results = service.FindItems(targetFolderId, dayFilter, view);
foreach(var item in results.Items)
{
emails.Add((EmailMessage)item);
}
PropertySet properties = (BasePropertySet.FirstClassProperties);
service.LoadPropertiesForItems(emails, properties);
}
I'm not sure where this is breaking down. I tried showing a message box in the foreach loop that finds the folder with the specified name, and it appears the folder is never found. I know there is a folder with that display name in the shared inbox.
I'm not great at debugging and unfortunately my grasp of the EWS API is pretty shaky. Any suggestions as to what I'm missing are welcome.
I keep everything related to the inbox in a static class so I only have to worry about one instance.
There is some problems with you code first
Then select the appropriate inbox with
sharedMailbox = new Mailbox(Properties.Settings.Default.Inbox);
targetFolderId = new FolderId(WellKnownFolderName.Inbox, sharedMailbox);
Nothing wrong with this code but you probably need to understand what's happening here. This code doesn't make any calls to the server it just setups a FolderId class that you can use in a call to get one of the well known folder.
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
view.PropertySet.Add(FolderSchema.DisplayName);
view.Traversal = FolderTraversal.Deep;
folderResults = service.FindFolders(WellKnownFolderName.Inbox, view);
This code would just search the Inbox Subfolder of the Mailbox who's credentials you are using. eg
service.Credentials = new WebCredentials(username, password);
If you wanted to Search the SubFolders of the Inbox of the SharedMailbox you would use
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
view.PropertySet.Add(FolderSchema.DisplayName);
view.Traversal = FolderTraversal.Deep;
folderResults = service.FindFolders(targetFolderId , view);
because you are using the targetFolderId in the FindFolder operations that is telling Exchange to Search the Shared Mailbox rather then the mailbox associated with the credentials you are using.

C# Console Application - Parsing Office 365 Inbox

I was able to succeed via a package I found called EAGetMail. Unfortunately, I realized soon after that they have a token system and this is not a free approach.
There are a couple other choices available, like using Outlook Mail REST API, and MimeKit, but I'm lost on how to achieve my end result because no "start to finish" code is available on either of these references that demonstrates how to parse an Inbox for an account.
I've started to write this with the help of Mimekit, but am not sure if this is the proper way at all.
I must imagine it looks something like:
using (var client = new SmtpClient ())
{
client.Connect("outlook.office365.com", 587);
client.Authenticate("myemail#office365account.com", "mypassword");
var message = MimeMessage.Load(stream);
}
I don't know how to setup the stream mentioned above, and I don't know if it's possible to do this with Mimekit and Office 365.
I'm open to seeing a solution for this in any other approach that's not through EAGetMail. If anyone has a lightweight solution ranging from actual establishing a connection, to pulling messages from the inbox, would be great to see!
I've got it using EWS (Exchange Web Services). Here's my code:
private static bool RedirectionUrlValidationCallback(string redirectionUrl)
{
// The default for the validation callback is to reject the URL.
bool result = false;
Uri redirectionUri = new Uri(redirectionUrl);
// Validate the contents of the redirection URL. In this simple validation
// callback, the redirection URL is considered valid if it is using HTTPS
// to encrypt the authentication credentials.
if (redirectionUri.Scheme == "https")
{
result = true;
}
return result;
}
static void Main(string[] args)
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials("email#myemail.com", "myPassword");
service.AutodiscoverUrl("email#myemail.com", RedirectionUrlValidationCallback);
//creates an object that will represent the desired mailbox
Mailbox mb = new Mailbox(#"email#myemail.com");
//creates a folder object that will point to inbox folder
FolderId fid = new FolderId(WellKnownFolderName.Inbox, mb);
//this will bind the mailbox you're looking for using your service instance
Folder inbox = Folder.Bind(service, fid);
//load items from mailbox inbox folder
if (inbox != null)
{
FindItemsResults<Item> items = inbox.FindItems(new ItemView(100));
foreach (var item in items)
{
item.Load();
Console.WriteLine("Subject: " + item.Subject);
}
}
}

Allow impersonation using ews

I'm trying to create a program in C# which should make it able to create appointments in someone else's Outlook calendar. I have code which makes it able to create appointments in my own calendar. I searched on Google and I found I should use impersonation. So I added the line:
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
This is my code:
private void button2_Click(object sender, EventArgs e) {
try{
ExchangeService service = new ExchangeService();
service.UseDefaultCredentials = true;
service.Credentials = new WebCredentials("Test#domain.com", "password");
service.AutodiscoverUrl("Test#domain.com", adAutoDiscoCallBack);
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "Test2#domain.com");
Appointment appointment = new Appointment(service);
// Set the properties on the appointment object to create the appointment.
appointment.Subject = "Tennis lesson";
appointment.Body = "Focus on backhand this week.";
appointment.Start = DateTime.Now.AddDays(2);
appointment.End = appointment.Start.AddHours(1);
appointment.Location = "Tennis club";
appointment.ReminderDueBy = DateTime.Now;
// Save the appointment to your calendar.
appointment.Save(SendInvitationsMode.SendToNone);
// Verify that the appointment was created by using the appointment's item ID.
Item item = Item.Bind(service, appointment.Id, new PropertySet(ItemSchema.Subject));
}
}catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
internal static bool adAutoDiscoCallBack(string redirectionUrl) {
// The default for the validation callback is to reject the URL.
bool result = false;
Uri redirectionUri = new Uri(redirectionUrl);
// Validate the contents of the redirection URL. In this simple validation
// callback, the redirection URL is considered valid if it is using HTTPS
// to encrypt the authentication credentials.
if (redirectionUri.Scheme == "https") {
result = true;
}
return result;
}
The problem is that I keep getting this error ""The SMTP-address has no mailbox associated with it."
Is it because impersonation isn't allowed on the server? If so how do I allow it?
I hope someone can help.
Ps: Sorry for the bad english
A few suggestions if this is Office365 or Exchange 2013 you first need the PrimarySMTP address of the Mailbox you wish to access eg
String MailboxToAccess = "PrimarySMTP#domain.demo";
Note for some tenants the SMTP and UPN/Logon are the same but that is not always the case.
This is what user you are going to be impersonating eg
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, MailboxToAccess);
You should also add in the X-AnchorMailbox header https://learn.microsoft.com/en-us/archive/blogs/webdav_101/best-practices-ews-authentication-and-access-issues eg
service.HttpHeaders.Add("X-AnchorMailbox", MailboxToAccess);
Also when you go to save the Appointment use the FolderId class with the Mailbox overload to ensure your hitting the correct Mailbox eg
FolderId CalendarFolderId = new FolderId(WellKnownFolderName.Calendar, MailboxToAccess);
appointment.Save(CalendarFolderId,SendInvitationsMode.SendToNone);
Cheers
Glen

Exchange web services - Forward Email as Attachment

I'm currently using Exchange Web Services in C#. I basically have a small application that reads emails from an inbox and process them.
I would like to forward those emails I receive as an attachment of an email. This attachment will be an outlook email that will include the original email (including its own attachments if any).
Any ideas?
Thanks!
EDIT:
Not sure why I'm getting the down votes but it seems this is not possible as the EWS API does not provide such functionality
You can create an ItemAttachment with EWS but you can't replicate fully what is possible in Outlook with MAPI. Eg with EWS you can create an ItemAttachment and then use the MIMEContent to create an attachment based on a current message as a workaround eg
FolderId Inboxid = new FolderId(WellKnownFolderName.Inbox, "target#domain.com");
ItemView InboxItemView = new ItemView(1);
FindItemsResults<Item> inFiResuls = service.FindItems(Inboxid,InboxItemView);
if(inFiResuls.Items.Count == 1){
EmailMessage fwdMail = new EmailMessage(service);
EmailMessage orgMail = (EmailMessage)inFiResuls.Items[0];
PropertySet psPropSet = new PropertySet(BasePropertySet.FirstClassProperties);
psPropSet.Add(ItemSchema.MimeContent);
orgMail.Load(psPropSet);
ItemAttachment emAttach = fwdMail.Attachments.AddItemAttachment<EmailMessage>();
emAttach.Item.MimeContent = orgMail.MimeContent;
ExtendedPropertyDefinition pr_flags = new ExtendedPropertyDefinition(3591,MapiPropertyType.Integer);
emAttach.Item.SetExtendedProperty(pr_flags,"1");
emAttach.Name = orgMail.Subject;
fwdMail.Subject = "see Attached";
fwdMail.ToRecipients.Add("user#domain.com");
fwdMail.Send();
}
This however doesn't give full fidelity of all the mapi properties associated with a particular message as the MIMEContent is just that, for most normal email messages this isn't an issue however for a message with an attached Outlook Task or other TNEF attachment then you would loose these of attachments or for other properties like categories,flags you would loose these also (you could copy these manually if needed).
Cheers
Glen
you can forward your email using this way. It first loads and reads the each emails with attachment who has "msg" extension. then forwards it to given address. see the below code
FindItemsResults<Item> findResults = exchange.FindItems(WellKnownFolderName.Inbox, newItemView(50,50));
Item[] msgItems = findResults.Where(msgItem => msgItem.HasAttachments).ToArray();
EmailMessage msgInfo = null;
var fileExtensions = new List<string> { "msg", "MSG", "Msg" };
foreach (MSEWS.Item msgItem in msgItems )
{
msgInfo = EmailMessage.Bind(exchange, msgItem.Id);
FileAttachment fa = msgInfo.Attachments[0] asFileAttachment;
if (fileExtensions.Any(ext => ext.Contains(fa.Name.Substring(fa.Name.Length - 3))))
{
fa.Load();
ResponseMessage responseMessage = msgInfo.CreateForward();
responseMessage.BodyPrefix = "messageBody";
responseMessage.ToRecipients.Add("toAddress");
responseMessage.Subject = "subject";
responseMessage.SendAndSaveCopy();
}
}

Categories

Resources