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);
}
Related
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.
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);
}
}
}
I first send a proactive message to the user via sms channel inside OAuthCallback method
var connector = new ConnectorClient();
Message message = new Message();
message.From = new ChannelAccount { Id = Constants.botId, Address = "+12312311", ChannelId = "sms", IsBot = true };
message.To = new ChannelAccount { Id = newUserId, Address = "+18768763", ChannelId = "sms", IsBot = false };
message.Text = $"How are you doing? ";
message.Language = "en";
connector.Messages.SendMessage(message);
IBotData myDataBag = new JObjectBotData(message);
myDataBag.UserData.SetValue("Username", "Bob");
myDataBag.PerUserInConversationData.SetValue("Newuser", "yes");
Then in my main Dialog.cs I try to access it
public static readonly IDialog<string> dialog = Chain
.PostToChain()
.Switch(new Case<Message, IDialog<string>>((msg) =>
{
var regex = new Regex("hello$", RegexOptions.IgnoreCase);
return regex.IsMatch(msg.Text);
},
(ctx, msg) =>
{
// Clearing user related data upon logout
string isnewuser = ctx.PerUserInConversationData.TryGetValue("Newuser");
string username = ctx.UserData.TryGetValue("Username");
return Chain.Return($"Welcome {username}");
}))
.Unwrap()
.PostToUser();
I receive the message on my phone. However, I am not able to get back the username and newuser session data saved inside OAuthCallback.
I suspect that this is happening because the proactive message does not have conversationId set. And the conversationId must differ somehow.
so how can I get it to set session data to my proactive message in the future conversation?
In proactive's scenarios, the conversation Id for channels change when the user answers your message, it's like a new session, we do this type of features using the channel data, but this solution is only for small data, you also have the option of creating a persistent session using the same table storage that the bot framework is using to save the dialog context, in this solution you can create another table to store your data serialized, and the final one is a persistent session using a distributed cache like Redis, but this type of services are expensive, so you have to analyze which type of solution is the right one for your solution, but as a start, you should try with the Channel Data property and if it works, you can analyze another approach
I hope I have been helpful
Not sure if this is still relevant after four years, but I think I figured this out in Access UserProfile from NotifyBot. Check it out.
I have an application that has been working fine for several weeks, basically it polls an Exchange 2010 mail server mailbox for new messages, gets the details of each one and any attachments, and use that information to create cases (with attachments) on the SalesForce CRM.
After each case is created from the email, the email must be marked as read by retrieving it from Exchange using the stored Item ID and calling the property set.
For several weeks this has been working fine, even getting some extra properties from Exchange such as the Subject, From, To properties which I can use in the session log.
Suddenly today the mail isn't being returned by Exchange from the itemID if I try to include the PropSet - if I omit the PropSet it works fine.
here is the code:
try
{
//creates an object that will represent the desired mailbox
Mailbox mb = new Mailbox(common.strInboxURL); // new Mailbox(targetEmailAddress); #"bbtest#bocuk.local"
//creates a folder object that will point to inbox fold
FolderId fid = new FolderId(WellKnownFolderName.Inbox, mb);
//this will bind the mailbox you're looking for using your service instance
Microsoft.Exchange.WebServices.Data.Folder inbox = Microsoft.Exchange.WebServices.Data.Folder.Bind(service, fid);
// As a best practice, limit the properties returned to only those that are required.
PropertySet propSet = new PropertySet(BasePropertySet.IdOnly,
ItemSchema.Subject, EmailMessageSchema.IsRead, EmailMessageSchema.Sender, EmailMessageSchema.DateTimeReceived);
// Bind to the existing item by using the ItemId.
// This method call results in a GetItem call to EWS.
EmailMessage mail = EmailMessage.Bind(service, itemId); //, propSet);
if (!mail.IsRead) // check that you don't update and create unneeded traffic
{
mail.IsRead = true; // mark as read
mail.Update(ConflictResolutionMode.AutoResolve); // persist changes
}
mail.Update(ConflictResolutionMode.AlwaysOverwrite);
e2cSessionLog("\tcommon.MarkAsRead", "email Item ID: " + itemId);
//e2cSessionLog("\tcommon.MarkAsRead", "MARKED AS READ email Subject: " + mail.Subject.ToString() + "; From: " + mail.Sender.Name, mail.DateTimeReceived.ToString());
return true;
}
catch (Exception ex)
{
string innerException = "";
...
notice that in the line EmailMessage mail = EmailMessage.Bind(service, itemId); //, propSet); I have now omitted the PropSet argument and it now works.
But why?
Is there something else I need to do in order to always get the properties back with the mail returned using Item ID?
Any thoughts?
I'm currently pulling emails from an exchange inbox like so...
var exchangeService = new ExchangeService(ExchangeVersion.Exchange2007_SP1)
{
Credentials = new NetworkCredential("user", "password", "domain")
};
exchangeService.AutodiscoverUrl("user#domain.com");
var emails = exchangeService.FindItems(WellKnownFolderName.Inbox, new ItemView(5));
foreach (var email in emails)
{
//var senderEmail = email.???
}
The email object doesn't seem to have any property for getting the sender's email address. How do I get that?
Here's some quick source I pulled from a working project example.
Basically, you can get minor details just by casting your result to an EmailMessage. However if you want to get richer details about the sender (display name, etc.) then you have to make a specific, additional bind (Web service request) against the message.
findResults = exchangeService.FindItems(folder.Id, messageFilter, view);
foreach (Item item in findResults)
{
if (item is EmailMessage)
{
EmailMessage message;
if (!toFromDetails)
message = (EmailMessage)item;
else
message = EmailMessage.Bind(exchangeService, item.Id);
As you can see in this code, I have an option to perform the additional bind, because it can take awhile, and I'm often dealing with thousands of results from hundreds of mailboxes. Sometimes the additional time may not be worth it to a particular customer.