I have following requirement :
Allow the user to drag & drop an email from outlook to a datagrid
Prepare a reply mail, and show it so the user can review and send
After sending, also fetch the send mail and put it into the datagrid
The drag/drop I have working
Preparing the replay email and showing it to the user I also have working, with this code :
MailItem mail = GetMailBySubject(dateReceived, subject);
if (mail != null)
{
MailItem mailReply = mail.ReplyAll();
// add text and stuff to mailReply...
mailReply.Display();
}
This will open a window in outlook, as if the user clicked reply in outlook.
Now I am stuck with the 3th requirement,
after the user send the reply email, I need somehow to find this email in outlook to add it to my datagrid.
But I have no clue on how to do that.
All I have is the original mail that is been used to prepare the reply.
Is there a way to find the reply with only this, or is this maybe a complete wrong approach ?
To make it more difficult is that I have to show the reply email NON Modal, so I have no trigger when the user clicked on send in outlook.
for reference, here is the code for GetMailBySubject
private MailItem GetMailBySubject(DateTime dateReceived, string subject)
{
MailItem Result = null;
Microsoft.Office.Interop.Outlook.Application OutlookIns = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.NameSpace olNamespace = OutlookIns.GetNamespace("MAPI");
MAPIFolder myInbox = olNamespace.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
Items items = myInbox.Items;
int count = items.Count;
MailItem mail = null;
int i = 1; //DO NOT START ON 0
while ((i < count) && (Result == null))
{
if (items[i] is MailItem)
{
mail = (MailItem)items[i];
if ((mail.ReceivedTime.ToString("yyyyMMdd hh:mm:ss") == dateReceived.ToString("yyyyMMdd hh:mm:ss")) && (mail.Subject == subject))
{
Result = mail;
}
}
i++;
}
return Result;
}
EDIT
I tried this code as suggested, but the Items.ItemAdd event is not firing.
So I must still be doing something wrong but I cant see it
this is my code now
MailItem mail = GetMailBySubject((DateTime)sentOn, msg.Subject);
if (mail != null)
{
MailItem mailReply = mail.ReplyAll();
mailReply.HTMLBody = GetDefaultReplyText() + Environment.NewLine + mailReply.HTMLBody;
Guid guid = Guid.NewGuid();
UserProperties mailUserProperties = null;
UserProperty mailUserProperty = null;
mailUserProperties = mailReply.UserProperties;
mailUserProperty = mailUserProperties.Add("myproperty", OlUserPropertyType.olText);
mailUserProperty.Value = guid.ToString(); ;
// the code below gives error "The property cannot be parsed or has an invalid format"
//mailReply.PropertyAccessor.SetProperty("myproperty", guid.ToString());
mailReply.Display();
}
private MailItem GetMailBySubject(DateTime dateReceived, string subject)
{
MailItem Result = null;
Microsoft.Office.Interop.Outlook.Application OutlookIns = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.NameSpace olNamespace = OutlookIns.GetNamespace("MAPI");
MAPIFolder myInbox = olNamespace.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
Items items = myInbox.Items;
int count = items.Count;
MailItem mail = null;
int i = 1; //DO NOT START ON 0
while ((i < count) && (Result == null))
{
if (items[i] is MailItem)
{
mail = (MailItem)items[i];
if ((mail.ReceivedTime.ToString("yyyyMMdd hh:mm:ss") == dateReceived.ToString("yyyyMMdd hh:mm:ss")) && (mail.Subject == subject))
{
Result = mail;
MAPIFolder mySent = olNamespace.GetDefaultFolder(OlDefaultFolders.olFolderSentMail);
mySent.Items.ItemAdd += Items_SentMailItemAdd;
}
}
i++;
}
return Result;
}
and finally
private void Items_SentMailItemAdd(object Item)
{
//throw new NotImplementedException();
; // this event is never fired
}
You can use the Items.ItemAdd event on the Sent Items folder. To check if that is your message, set a custom property on the message that you create and display. You can use MailItem.UserProperties.Add, but that can force the message to be sent in the TNEF format. To prevent that from happening, you can use MailItem.PropertyAccessro.SetProperty to set a named MAPI property without using the UserProperties collection. You can set a test user property and look at its DASL name (to be used by SetProperty) with OutlookSpy (I am its author - select the message, click IMessage button, select your custom property, see the DASL edit box).
Related
I have created a custom VSTO plugin in Outlook 2016 that asks the user to attach a file, the file is then added to an e-mail reply and automatically sent.
public void Attachment_Click(Office.IRibbonControl control)
{
Outlook.Explorer explorer = Globals.ThisAddIn.Application.ActiveExplorer();
if (explorer != null)
{
Outlook.Selection selection = explorer.Selection;
if (selection.Count >= 1)
{
Outlook.MailItem mailItem = selection[1] as Outlook.MailItem;
OpenFileDialog attachment = new OpenFileDialog();
attachment.Title = "Add your file";
attachment.ShowDialog();
if (mailItem != null) //could be something other than MailItem
{
Outlook.MailItem response = mailItem.ReplyAll();
bool retValue = false;
response.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
response.HTMLBody = "<p>MESSAGE</p>" + response.HTMLBody;
response.Attachments.Add(attachment.FileName, Outlook.OlAttachmentType.olByValue, 1,"Attachment.pdf");
response.Send();
mailItem.Delete();
}
}
}
}
I am attempting to tweak the code so it automatically adds a different attachment depending on the user logged in to Outlook, e.g. if user=user1#company.com add '\fileshare\file1.pdf', if user=user2#company.com add '\fileshare\file2.pdf'
Is this possible?
I am attempting to convert the current user address to string using the below but not had any success:
{
return
Globals.ThisAddIn.Application.ActiveExplorer().Session.CurrentUser.AddressEntry.GetExchangeUser().PrimarySmtpAddress.ToString();
}
Your code will fail if there is no active explorer, but you really do not need it.
Off the top of my head:
AddressEntry cu = Globals.ThisAddIn.Application.Session.CurrentUser.AddressEntry;
ExchangeUser eu = cu.GetExchangeUser();
return (eu == null) ? cu.Address : eu.PrimarySmtpAddress;
I've been having a little weekend project for myselff which involves getting all my ToDo tasks from Outlook, put them in a DataGridView and me being able to edit and export them.
The only problem I've been running into is me being unable to get the task specific properties for flagged emails while they still exist, I just don't see any way to access them.
Here is a portion of my current code.
private void retrieveTasks()
{
//Clear datagrid so we won't have duplicate information
taskList.Rows.Clear();
//Define some properties so we can use these to retrieve the tasks
Outlook.Application app = null;
_NameSpace ns = null;
Store outlookStore = null;
Outlook.MAPIFolder taskFolder = null;
Outlook.MAPIFolder specialFolder = null;
TaskItem task = null;
DateTime nonDate = new DateTime(4501, 1, 1);
try
{
//Connect to Outlook via MAPI
app = new Outlook.Application();
ns = app.GetNamespace("MAPI");
ns.Logon(null, null, false, false);
/*
outlookStore = ns.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox).Store;
taskFolder = outlookStore.GetSpecialFolder(Microsoft.Office.Interop.Outlook.OlSpecialFolders.olSpecialFolderAllTasks);
*/
//Get the taskfolder containing the Outlook tasks
//taskFolder = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderTasks);
outlookStore = ns.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox).Store;
taskFolder = outlookStore.GetSpecialFolder(OlSpecialFolders.olSpecialFolderAllTasks);
//Console.WriteLine(specialFolder.Items[1].Subject.ToString());
/*for (int i = 1; i <= specialFolder.Items.Count; i++)
{
//task = (Outlook.TaskItem)specialFolder.Items[i];
Console.WriteLine(specialFolder.Items[i].ToString());
}*/
for (int i = 1; i <= taskFolder.Items.Count; i++)
{
//Get task from taskfolder
Object item = taskFolder.Items[i];
if (item is Outlook.MailItem)
{
MailItem mail = (Outlook.MailItem)item;
if (mail.TaskCompletedDate.Equals(nonDate))
{
string percentComplete = "";
string taskPrio = "";
if (mail.UserProperties.Find("Prio") == null)
{
taskPrio = "";
}
else
{
taskPrio = mail.UserProperties.Find("Prio").Value.ToString();
}
//mail.UserProperties.
if (mail.UserProperties.Find("% Complete") == null)
{
percentComplete = "";
}
else
{
percentComplete = mail.UserProperties.Find("% Complete").Value.ToString();
}
//Add the tasks details to the datagrid
taskList.Rows.Add(
i.ToString(),
checkForNull(mail.Subject),
parseDate(mail.TaskStartDate.ToString()),
parseDate(mail.TaskDueDate.ToString()),
percentComplete, "Taak voltooid",
//statusToFriendlyName(mail.Status.ToString()),
taskPrio
);
}
}
else if (item is Outlook.TaskItem)
{
task = (Outlook.TaskItem)item;
Console.WriteLine(task.Subject);
if (task.Complete == false)
{
string taskPrio = "";
//Make sure custom task property is failed or set it to empty text to prevent crashes
if (task.UserProperties.Find("Prio") == null)
{
taskPrio = "";
}
else
{
taskPrio = task.UserProperties.Find("Prio").Value.ToString();
}
//Add the tasks details to the datagrid
taskList.Rows.Add(
i.ToString(),
task.Subject.ToString(),
parseDate(task.StartDate.ToString()),
parseDate(task.DueDate.ToString()),
task.PercentComplete.ToString(),
statusToFriendlyName(task.Status.ToString()),
taskPrio
);
}
}
}
}
catch (System.Runtime.InteropServices.COMException ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
//Release Outlook sources
ns = null;
app = null;
}
}
So specifically I am looking for the "% complete" property and the status property, everything else I am able to work around on.
It would make my life SO MUCH EASIER if I can get this to work :)
Hope to hear from anyone on here!
Jeffrey
Do you mean you need to retrieve the task specific properties from a MailItem object (as opposed to the TaskItem object)?
Take a look at the message with OutlookSpy (I am its author) - click IMessage button, look at the MAPI properties in the GetProps tab. Any property can be accessed using MailItem.PropertyAccessor.GetProperty. The DASL name to be used by GetProperty is displayed by OutlookSpy (select the property in the IMessage window, see the DASL edit box).
I am working on a software where i am able to retrieve the inbox and sent items from outlook. What i want to do is to relate the inbox emails with replies (if someone has sent a reply to that email). The list should be displayed in this order
Sender#abc.com Incoming Subject Received Time
sender#abc.com Reply Subject Sent time
What i am planning to do is to retrieve inbox items in one datatable and the sent items in another datatable. It reads the emails one by one on the basis of sender email and the subject, then searches that sender and email in the sent items and if it matches, merge that to a third datatable.
is there any other better way to do so?
Here is the code:
private DataTable GetInboxItems()
{
DataTable inboxTable;
//try
//{
filter = "[ReceivedTime] >= '" + dtpStartDate.Value.ToString("dd/MM/yyyy 12:00 AM") + "' and [ReceivedTime] <= '" + dtpEndDate.Value.ToString("dd/MM/yyyy 11:59 PM") + "'";
Outlook.Application outlookApp = GetApplicationObject();
Outlook.Folder root = outlookApp.Session.DefaultStore.GetRootFolder() as Outlook.Folder;
EnumerateFolders(root);
//string filter = "[ReceivedTime] > '" + dtpStartDate.Value.ToString("dd/MM/yyyy") + "'";
//inbox
Outlook.MAPIFolder inboxFolder = outlookApp.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
inboxTable = CreateTable();
int count = 0;
if (inboxFolder.Items.Count > 0)
{
var restrictedItems = inboxFolder.Items.Restrict(filter);
restrictedItems.Sort("[ReceivedTime]", true); //descending
//foreach (var item in inboxFolder.Items)
foreach (var item in restrictedItems)
{
var mail = item as Outlook.MailItem;
if (mail != null)
{
//try
//{
DataRow row = inboxTable.NewRow();
//row["sn"] = (++count).ToString();
row["sn"] = mail.EntryID + " " + mail.ReceivedByEntryID;
row["MailType"] = "Inbox";
row["SenderName"] = mail.SenderName;
row["SenderEmail"] = mail.SenderEmailAddress;
row["ReceivedDate"] = mail.ReceivedTime;
row["Subject"] = mail.Subject;
row["Body"] = mail.Body != null ? (mail.Body.Length > 25 ? mail.Body.Substring(0, 25) : mail.Body) : null;
//row["Body"] = mail.Body != null ? mail.Body : "";
row["MailSize"] = mail.Size.ToString();
string attachments = null;
if (mail.Attachments.Count > 0)
{
foreach (var attachment in mail.Attachments)
{
if (((Outlook.Attachment)attachment) != null)
//attachments = ((Outlook.Attachment)attachment).FileName + " " + ((Outlook.Attachment)attachment).Size.ToString() + ", ";
attachments += (((Outlook.Attachment)attachment).Size / 1024).ToString() + " KB, ";
}
}
row["AttachmentCount"] = mail.Attachments.Count;
if (attachments != null)
row["AttachmentSize"] = attachments.Substring(0, attachments.Length - 2);
inboxTable.Rows.Add(row);
}
//catch (Exception ex)
//{
// return null;
//}
}
}
return inboxTable;
}
I've made this kind of stuff in an outlook add-in
There is no 100% way to make it ...
The problem is that the conversation ID is not always kept by other software. So you need to use a set of data to link email to their answers:
- Message-ID: this is available in the email headers. Sent items doesn't have this :/
- In-Reply-To: this is also in the email headers
- Topic ID
For the topic ID, I retrieve values in this order (I take the first available):
- MailItem.ConversationIndex: Each reply add bytes to the conversation index
- Reference header
Then, I link email to their reply using topicID, email have the same first X characters than reply. Example original email topic id = abc, reply = abcdef
For all mails than cannot be linked using conversation id, I try to link using Message-ID & In-Reply-To ID
Problem will particularly comes from email sent by outlook (no Message-ID) then user reply without Reference/ConversationIndex header... you'll not have any way to link both mail together.
Hope it helps
EDIT: Here's some code. I've copy/pasted code from different class/method to create a single method, so it may not compile. Take it more as a pseudo-code.
public SimpleTree<MailData> CreateTree(List<MailData> mails)
{
mails.Sort((m1, m2) => m1.TopicId == m2.TopicId ? m2.CreationDate.CompareTo(m1.CreationDate) : m1.TopicId.CompareTo(m2.TopicId));
var tree = new SimpleTree<MailData>();
var i = 0;
while (i < mails.Count)
{
var node = tree.Children.Add(mails[i]);
var topicId = mails[i].TopicId;
var start = i + 1;
while (start < mails.Count
&& !string.IsNullOrEmpty(topicId)
&& !string.IsNullOrEmpty(mails[start].TopicId)
&& mails[start].TopicId.StartsWith(topicId))
{
node.Children.Add(mails[start]);
start++;
}
i = start;
}
// Handle email where TopicId are different, but ParentId is filled with correct value
for (int j = tree.Children.Count - 1; j >= 0; j--)
{
var child = tree.Children[j];
if (child.Children.Count == 0 && !string.IsNullOrEmpty(child.Value.ParentId))
{
var parentNode = tree.FindNode(s => s != null && s.MessageId == child.Value.ParentId);
if (parentNode != null && parentNode != child)
parentNode.Children.Add(child);
}
}
return tree;
}
MailData is a class with the 3 fields needed as explained before:
MessageID (from Message-ID header)
ParentId (from In-Reply-To header)
TopicId (from ConversationIndex or Reference header)
SimpleTree<> is a class to create tree, it's in fact a node with children. Nothing special or related to email here. The .Value property refer to the data associated to the node (MailData here)
The goal is to sort on the TopicId so that we can construct the tree in 1 loop
Then I check all the mail in the tree root to check if we can move them under another mail usine MessageId/ParentId
Just remember that it create a one-level tree, something like:
Mail A
Reply AA
Reply AAA
Mail B
Reply BB
Reply BBB
But you would need something like this:
Mail A
Reply AA
Reply AAA
Mail B
Reply BB
Reply BBB
I am using EWS to create a StreamingSubscription on an inbox. It is listening for the NewMail event. I am able to pull the From Address, Subject, Body, To Address, CC Address but not the BCC Address. Is there any way to see this list?
CODE:
static void OnEvent(object sender, NotificationEventArgs args)
{
String from = null;
String subject = null;
String body = null;
String to = null;
StreamingSubscription subscription = args.Subscription;
// Loop Through All Item-Related Events
foreach (NotificationEvent notification in args.Events)
{
ItemEvent item = (ItemEvent)notification;
PropertySet propertySet = new PropertySet(ItemSchema.UniqueBody);
propertySet.RequestedBodyType = BodyType.Text;
propertySet.BasePropertySet = BasePropertySet.FirstClassProperties;
// Parse Email
EmailMessage message = EmailMessage.Bind(service, item.ItemId, propertySet);
from = message.From.Address;
subject = message.Subject;
body = message.Body.Text;
if (message.ToRecipients.Count > 0)
{
to = message.ToRecipients[0].Address;
body += "\n TO FIELD";
}
else if (message.CcRecipients.Count > 0)
{
to = message.CcRecipients[0].Address;
body += "\n CC FIELD";
}
/************** Does not work! BccRecipients is always empty *****************/
else if (message.BccRecipients.Count > 0)
{
to = message.BccRecipients[0].Address;
body += "\n BCC FIELD";
}
/************* REST OF CODE ************************/
}
}
That would kind of defeat the point of a blind-carbon-copy. I dont believe it can be done.
Consider using the Journaling feature of Exchange. This uses something called "Envelope Journaling" which includes BCC information for messages within the Exchange environment.
For everything that comes from external sources (gmail) no BCC information is available.
This might help:
http://gsexdev.blogspot.com/2011/06/processing-bccs-in-exchange-transport.html
I'm attempting to get the email address typed into the To field of a compose mail window.
I try to get the Address property of a Recipient, which according to VS, should give me the email.
I am instead receiving a string that looks like this:
"/c=US/a=att/p=Microsoft/o=Finance/ou=Purchasing/s=Furthur/g=Joe"
How can I get the email address in the recipient field?
My code so far:
List <string> emails = new List<string>();
if (thisMailItem.Recipients.Count > 0)
{
foreach (Recipient rec in thisMailItem.Recipients)
{
emails.Add(rec.Address);
}
}
return emails;
Can you try this ?
emails.Add(rec.AddressEntry.Address);
Reference link
EDIT:
I don't have the right environment to test so I'm just guessing all this, but how about
string email1Address = rec.AddressEntry.GetContact().Email1Address;
or .Email2Adress or .Email3Address
Also there is,
rec.AddressEntry.GetExchangeUser().Address
that you might want to try.
Try this
private string GetSMTPAddressForRecipients(Recipient recip)
{
const string PR_SMTP_ADDRESS =
"http://schemas.microsoft.com/mapi/proptag/0x39FE001E";
PropertyAccessor pa = recip.PropertyAccessor;
string smtpAddress = pa.GetProperty(PR_SMTP_ADDRESS).ToString();
return smtpAddress;
}
This is available on MSDN here
I have used the same way to get email addresses in my application and its working.
the AddressEntry also has an SMTPAddress property that exposes the primary smtp adress of the user.
I don't know if this helps or how accurate
it is, a sample
private string GetSmtp(Outlook.MailItem item)
{
try
{
if (item == null || item.Recipients == null || item.Recipients[1] == null) return "";
var outlook = new Outlook.Application();
var session = outlook.GetNamespace("MAPI");
session.Logon("", "", false, false);
var entryId = item.Recipients[1].EntryID;
string address = session.GetAddressEntryFromID(entryId).GetExchangeUser().PrimarySmtpAddress;
if (string.IsNullOrEmpty(address))
{
var rec = item.Recipients[1];
var contact = rec.AddressEntry.GetExchangeUser();
if (contact != null)
address = contact.PrimarySmtpAddress;
}
if (string.IsNullOrEmpty(address))
{
var rec = item.Recipients[1];
var contact = rec.AddressEntry.GetContact();
if (contact != null)
address = contact.Email1Address;
}
return address;
}
finally
{
}
}