My company needs an add-in for automatically adding offers to the email when it is the first time we send an email to a recipient.
My question is :
How can I check if this is the first time the user sends an email to the recipients?
I tried this but I receive error that Recipient is unknown property. And I also think that this is not the right approach...
object folderItem;
Boolean AlreadyEmailed = false;
if (mail != null)
{
const string PR_SMTP_ADDRESS =
"http://schemas.microsoft.com/mapi/proptag/0x39FE001E";
Outlook.Recipients recips = mail.Recipients;
foreach (Outlook.Recipient recip in recips)
{
Outlook.PropertyAccessor pa = recip.PropertyAccessor;
string smtpAddress =
pa.GetProperty(PR_SMTP_ADDRESS).ToString();
string filter = "[Recipient] = 'John#foo.com'";
filter = filter.Replace("John#foo.com", smtpAddress);
Debug.WriteLine(filter);
folderItem = items.Restrict(filter);
if(folderItem != null)
{
Debug.WriteLine("We found items that have the filter");
AlreadyEmailed = true;
}
//Debug.WriteLine(recip.Name + " SMTP=" + smtpAddress);
}
if(!AlreadyEmailed)
{
Debug.WriteLine("This is the first time we email ... ");
}
}
The Sent property of the MailItem class returns a Boolean value that indicates if a message has been sent. In general, there are three different kinds of messages: sent, posted, and saved. Sent messages are items sent to a recipient or public folder. Posted messages are created in a public folder. Saved messages are created and saved without either sending or posting.
Also you may use the following Extended MAPI properties that deal with the message state (replied/forwarded):
PR_ICON_INDEX (http://schemas.microsoft.com/mapi/proptag/0x10800003)
PR_LAST_VERB_EXECUTED (the DASL name is http://schemas.microsoft.com/mapi/proptag/0x10810003)
PR_LAST_VERB_EXECUTION_TIME (0x10820040)
To get these values use the PropertyAccessor class (see the corresponding properties of Outlook items).
Be aware, new Outlook items don't have the EntryID property set.
You can Use To/CC/BCC properties in Items.Find/Restrict. Note that it is better to use Find in your case since you only need a single match, not all of them. Also note that Restrict will not return null if no matches are found, but rather an Items collection with Items.Count == 0.
That being said, To/CC/BCC might not include addresses, only names, so search won't help you. You can still loop through all items in the folder and explicitly check the Recipients collection of each item, but that will be hugely inefficient.
On the Extended MAPI level (C++ or Delphi), one can create subrestrictions on message recipients (or attachments), but the Outlook Object Model does not expose that functionality.
If using Redemption is an option (I am its author), its implementation of Find/Restrict does support queries on the Recipients collection:
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set YourOutlookFolder = Application.ActiveExplorer.CurrentFolder
set rFolder = Session.GetFolderFromID(YourOutlookFolder.EntryID)
set rItems = rFolder.Items
set rMsg = rItems.Find("Recipients LIKE 'John#foo.com' ")
while not (rMsg Is Nothing)
Debug.print rMsg.Subject
set rMsg = rItems.FindNext
wend
In C# (not tested):
Redemption.RDOSession Session = new Redemption.RDOSession();
Session.MAPIOBJECT = Application.Session.MAPIOBJECT;
set rFolder = Session.GetFolderFromID(YourOutlookFolder.EntryID);
Redemption.RDOMail rMsg = rFolder.Items.Find("Recipients LIKE 'John#foo.com' ") ;
AlreadyEmailed = rMsg != null;
Related
we have encountered a problem with recognizing and setting ExtendedProperties by EWS. Right now, we have the functionality to import emails, which can be done either manually by user or a separate service. There seems to be inconsistency with how Exchange searches through ExtendedProperties and how they are set.
To clarify, I have three mailboxes attached to Outlook application. One is mine and two are the test accounts, which are linked to my AD account. The test accounts contain emails which are imported by the automatic service(when a mail is sent to one of them, it is imported to our application and there is set and ExtendedProperty on the email, providing the information that the email was imported).
What is strange, on my side, in Outlook Add-in, on one account the emails are properly marked and on the other it is not happening as it should, despite that the mechanism is the same.
These are the codes of our add-in.
This is the code of retrieving emails:
private static IEnumerable<EmailMessage> GetEmails(Microsoft.Exchange.WebServices.Data.Folder folder, int pageSize, DateTime lastImport)
{
SearchFilter filter = PrepareFilter(lastImport);
List<Item> foundItems = new List<Item>();
Guid propertySetId = new Guid("F723C954-3F83-46AA-A783-FDEAC90AE512");
ExtendedPropertyDefinition registered = new ExtendedPropertyDefinition(propertySetId, "Registered", MapiPropertyType.Boolean);
ItemView view = new ItemView(pageSize);
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties, registered);
foundItems = service.FindItems(folder.Id, filter, view).Items.ToList();
List<EmailMessage> results = new List<EmailMessage>();
foreach (EmailMessage message in foundItems)
{
results.Add(message);
}
return results;
}
private static SearchFilter PrepareFilter(DateTime lastImport)
{
Guid propertySetId = new Guid("F723C954-3F83-46AA-A783-FDEAC90AE512");
ExtendedPropertyDefinition Registered= new ExtendedPropertyDefinition(propertySetId, "Registered", MapiPropertyType.Boolean);
SearchFilter isRegistered = new SearchFilter.IsEqualTo(registered, true);
return isRegistered;
}
And this is the flag setting method written by my colleague:
public static bool SetExchangeRegistered(List<MailItem> mailsToRegister)
{
try
{
var service = new Exchange.ExchangeService(Exchange.ExchangeVersion.Exchange2010)
{
UseDefaultCredentials = true
};
var emailAddress = new Application().ActiveExplorer().Session.CurrentUser.AddressEntry.GetExchangeUser().PrimarySmtpAddress;
service.AutodiscoverUrl(emailAddress);
foreach (var mail in mailsToRegister)
{
string itemId = ConvertHexEntryIdToEwsId(service, mail.EntryID, emailAddress);
Exchange.EmailMessage message = Exchange.EmailMessage.Bind(service, new Exchange.ItemId(itemId));
bool propertyRegisteredExists =
message.ExtendedProperties.FirstOrDefault(x => x.PropertyDefinition.Name == "Registered") != null;
if (!propertyRegisteredExists)
{
Guid propertySetId = new Guid("F723C954-3F83-46AA-A783-FDEAC90AE512");
Exchange.ExtendedPropertyDefinition registered =
new Exchange.ExtendedPropertyDefinition(propertySetId, "Re", Exchange.MapiPropertyType.Boolean);
message.SetExtendedProperty(registered, true);
}
else
{
message.ExtendedProperties.First(x => x.PropertyDefinition.Name == "Registered").Value = true;
}
message.Update(Exchange.ConflictResolutionMode.AlwaysOverwrite);
}
}
catch (Exception ex)
{
grmtAddInBase.Logger.Trace(string.Format("Registered update failed: {0}", ex.Message));
return false;
}
grmtAddInBase.Logger.Trace("Email property 'Registered' updated successfully");
return true;
}
Generally, the method above seems to be identical in control flow to the method which is implemented in the separate service, and it seems that it actually does set the ExtendedProperty of the email correctly.
Another clue, which, to be honest, leaves me clueless, is that even when I tried to get all the emails greedily, load it and then separate the correct ones by their ExtendedProperty which we set... The problem then is that for some emails(i.e. inside one of the mailboxes which are treated by the autoimport service) it sees the Properties correctly and for the rest(i.e. my own mailbox) it doesn't even load and ExtProps, which means that it probably does not see them at all.
I have also tried to use DefaultExtendedPropertySet.PublicStrings, but then it didn't work at all.
I am a little bit puzzled and nobody in close proximity or EWS docs/MS Forums could provide the answer. I am aware that there may be no help, I am aware of the possibility that we are just hopelessly stupid and made some mistake which we cannot find.
After a while, the emails on Exchange mailboxes are just copies - that would be reasonable reason why we cannot access the ExtendedProperties of the email that we received, when it's "flag" was set by another user. But, maybe there is a way to synchronize those properties between those mailboxes? Because there are some alternatives already that we will discuss, but it would be nice if at least part of the current solution could be reused.
I want to programmatically add with C# recipients to an existing/composing MailItem. When I add a recipient like this:
Microsoft.Office.Interop.Outlook.Inspector inspector = Globals.ThisAddIn.Application.ActiveInspector();
Microsoft.Office.Interop.Outlook.MailItem item = inspector.CurrentItem as Microsoft.Office.Interop.Outlook.MailItem;
Microsoft.Office.Interop.Outlook.Recipient mRecipient = item.Recipients.Add("test.user");
mRecipient.Type = (int)Microsoft.Office.Interop.Outlook.OlMailRecipientType.olBCC;
it appears in the TO field of the MailItem.
When I do something like this:
item.BCC = "test.user";
it appears correclty...
Is there a way to to add the recipient with the first method (Type.olBCC) and show it up in the BCC mail field (2nd snippet)?
I want to go this way because then I can iterate through all recipients and remove some when a special condition is called.
The problem is when I remove the added BCC recipient with
item.BCC = "";
all recpients were deleted in the BCC field.
Apparently we are all missing the point: https://msdn.microsoft.com/en-us/vba/outlook-vba/articles/recipients-object-outlook
According to this
Microsoft.Office.Interop.Outlook.Recipient mRecipient = item.Recipients.Add("test.user");
mRecipient..Type = olCC // at a guess here olBCC would be BCC...
We can modify the list as a list.. :)
Note: I couldnt find olCC in my list.. but maybe I need to look harder.
Note2! Found it.
OlMailRecipientType. has olTo, olOriginator, olCC and olBCC
There you go. so mRecipient.Type = OlMailRecipientType.olBCC should do the trick
The following - opened a new mail item with joe.bloggs in bcc:
olApplication = new Microsoft.Office.Interop.Outlook.Application();
m = olApplication.CreateItem(OlItemType.olMailItem);
Recipient r = m.Recipients.Add("joe.bloggs");
r.Type = (int)OlMailRecipientType.olBCC;
m.Display(true);
Having saved a draft and worked out how to get hold of it as 1 item apparently isnt items[0] ..
the following also works:
Microsoft.Office.Interop.Outlook.MAPIFolder folderDrafts = ns.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderDrafts);
if (folderDrafts.Items.Count > 0)
{
m = (Microsoft.Office.Interop.Outlook.MailItem)folderDrafts.Items[1];
Recipient r = m.Recipients.Add("joe.bloggs");
r.Type = (int)OlMailRecipientType.olBCC;
m.Display(true);
}
As the user BugFinder mentioned there is a workaround neccessary since the UI adds the BCC recipient to the olTO field. I solved this with first adding a dummy, then add the BCC and remove the dummy:
Microsoft.Office.Interop.Outlook.Inspector inspector = Globals.ThisAddIn.Application.ActiveInspector();
Microsoft.Office.Interop.Outlook.MailItem item = inspector.CurrentItem as Microsoft.Office.Interop.Outlook.MailItem;
//Create dummy recipient object for UI bug
Microsoft.Office.Interop.Outlook.Recipient dummy =
item.Recipients.Add("bugDummy");
dummy.Type = (int)Microsoft.Office.Interop.Outlook.OlMailRecipientType.olBCC;
//Create the BCC object that will be used
Microsoft.Office.Interop.Outlook.Recipient mRecipient = item.Recipients.Add("test#user.de");
mRecipient.Type = (int)Microsoft.Office.Interop.Outlook.OlMailRecipientType.olBCC;
//iterate through the recipients and delete the dummy
foreach (Microsoft.Office.Interop.Outlook.Recipient recipient in item.Recipients)
{
if (string.Compare(recipient.Name, "bugDummy", true) == 0)
{
recipient.Delete();
break;
}
}
I am using EWS to add contact items to an office365 account.
Everything works fine, just one detail is not as expected.
When I create a new contact and add e.g. the home address like this:
if (ewsContact.PhysicalAddresses.Contains(PhysicalAddressKey.Home) == false)
{
ewsContact.PhysicalAddresses[PhysicalAddressKey.Home] = new PhysicalAddressEntry();
}
if (string.IsNullOrEmpty(contact.HomeZip) == false)
{
ewsContact.PhysicalAddresses[PhysicalAddressKey.Home].PostalCode = contact.HomeZip;
}
if (string.IsNullOrEmpty(contact.HomeCity) == false)
{
ewsContact.PhysicalAddresses[PhysicalAddressKey.Home].City = contact.HomeCity;
}
The data is written to the contact item, but in the combined field and on the card view in Outlook the ordering of Zip and City is always the order that is used in the US - e.g. Washington 98155
As I have a lot of addresses from europe, I need the correct order - e.g. 10115 Berlin.
If I open the contact in Outlook, change the Zip code at one position and save it back, the order is saved correctly and the display in Outlook is correct.
Is there any way to have the correct order with EWS?
You will need to set the PidLidWorkAddress property https://msdn.microsoft.com/en-us/library/office/cc815905.aspx which should contain the address information formatted in the locale of the client. So in EWS you need to set this using the Extended property definition eg
ExtendedPropertyDefinition PidLidWorkAddress = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.Address, 0x801B, MapiPropertyType.String);
ewsContact.SetExtendedProperty(PidLidWorkAddress, AddressValue);
I started using #jstedfast Mimekit/Mailkit library, which is great by the way, to pull undeliverables using its subject line for search. I tried using the message.to, message.resentto.
how do i get that information. My first try today. I was able to get the list and the body but I just need the email. I tried using s22.imap but there's no support anymore then I discovered this. I know you're active here #jstedfast I need you help..there's no discussion tab in your github.
Thanks in advance
If you look at the raw message source, does the value of the Content-Type header match something along the lines of multipart/report; report-type=delivery-status? If so, it is very likely that this message will have a sub-part with a Content-Type header with a value of message/delivery-status. It should be the second child part of the multipart/report (the first part should be a human-readable explanation).
If so, you can cast the message/delivery-status MimeEntity to an instance of a MessageDeliveryStatus. You'll notice that the MessageDeliveryStatus class has a property called StatusGroups which is a list of multiple collections of headers (e.g. a list of HeaderList objects).
Each HeaderList contains information about a particular recipient that failed. I think what you want to do is look at the Final-Recipient header which will contain 2 pieces of information:
The address-type (should typically be "rfc822")
The mailbox of the recipient (which is what you want)
Unfortunately, MimeKit does not have any tools to parse the Final-Recipient header, but it should be trivial to locate the end of the address-type parameter in the header value by using IndexOf (';') and then you can use something like MailboxAddress.TryParse() to parse it (or you could probably just Substring() the value w/o parsing).
So, the way this might look in code is this:
string[] GetUndeliverableAddresses (MimeMessage message)
{
var report = message.Body as MultipartReport;
if (report == null)
throw new ArgumentException ("This is not a multipart/report!");
MessageDeliveryStatus status = null;
for (int i = 0; i < report.Count; i++) {
status = report[i] as MessageDeliveryStatus;
if (status != null)
break;
}
if (status == null)
throw new ArgumentException ("Did not contain a message/delivery-status!");
var undeliverables = new List<string> ();
for (int i = 0; i < status.StatusGroups.Count; i++) {
var recipient = status.StatusGroups[i]["Final-Recipient"];
int semicolon = recipient.IndexOf (';');
var undeliverable = recipient.Substring (semicolon + 1).Trim ();
undeliverables.Add (undeliverable);
}
return undeliverables.ToArray ();
}
For more information on message/delivery-status, see https://www.rfc-editor.org/rfc/rfc3464
Hope that helps.
this is what I did
foreach (var uid in inbox.Search(query))
{
var message = inbox.GetMessage(uid);
// Console.WriteLine("Subject: {0}", message.Subject);
//Console.WriteLine("Subject: {0}", message.Headers);
// Console.WriteLine("Subject: {0}", message.BodyParts);
var text = message.BodyParts;
string src = text.ElementAt(1).ToString();
int srcStart = src.IndexOf("RFC822;",0); << i used final-recipient intially
int srcEnd = src.IndexOf("Action", 0);
Console.WriteLine("Email:" + src.Substring(srcStart + 8, srcEnd - (srcStart + 8)));
Console.WriteLine(src);
//foreach (var part in message.BodyParts)
//{
// Console.WriteLine(part);
// // do something
//}
}
let me know if there could be a problem..will I get the rfc822 if the recipient inbox is full? there's no way i can test that.. I tested with emails with nonexistent domain, mailbox does not exist..with live.com, randomdomain.com,yahoo.com and gmail. gmail, on the other hand does not return any undeliverables.
I cannot work out how I can do what I need to do.
I have a method called 'MarkAsRead' that takes an ItemID and should mark the mail item as read.
But it seems that the Item doesn't have an 'IsRead' proeprty, only an email does, so I need to cast my Exchange WebServices mail item to an email message.
here is the code:
try
{
System.Diagnostics.Debugger.Break();
//creates an object that will represent the desired mailbox
Mailbox mb = new Mailbox(common.strInboxURL);
//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);
//// if the property is not loaded yet, first load it
//mail.Load(PropertySet(BasePropertySet.IdOnly, EmailMessageSchema.IsRead));
//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
//}
// As a best practice, limit the properties returned to only those that are required.
PropertySet propSet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject);
// Bind to the existing item by using the ItemId.
// This method call results in a GetItem call to EWS.
Item item = Item.Bind(service, itemId, propSet);
EmailMessage mail = Item.Bind(service, itemId, propSet);
item.Load(new PropertySet(BasePropertySet.IdOnly,EmailMessageSchema.IsRead));
item.IsRead = true;
item.Update(ConflictResolutionMode.AlwaysOverwrite);
return true;
}
I am trying to do something like this:
// if the property is not loaded yet, first load it
mail.Load(PropertySet(BasePropertySet.IdOnly, EmailMessageSchema.IsRead));
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
}
unfortunately I need to be able to get a unique email message from the item.
how can I do that please?
Philip-
You can add EmailMessageSchema.IsRead to your property set, so that you can get it in your Bind call, and then you don't have to call Load at all.
PropertySet propSet = new PropertySet(BasePropertySet.IdOnly,
ItemSchema.Subject, EmailMessageSchema.IsRead);
The EmailClass derives from the Item class, so Bind works on both. So you can do the following:
EmailMessage mail = EmailMessage.Bind(service, itemId, propSet);
Put that together and you've got this:
PropertySet propSet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject, EmailMessageSchema.IsRead);
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
}
Philip,
When testing my code I added just a little bit to the Item.Bind() from your code to make this work:
EmailMessage mail = Item.Bind(service, itemId, propSet) as EmailMessage;
The as EmailMessage will cast it to the appropriate type for you. After that I was able to see the isRead property.
I hope this helps. If this does resolve your problem, please mark the post as answered.
Thanks,
--- Bob ---