Exchange 2007 NDRs to public Folders - c#

Exchange deletes Non-Delivery-Reports (NDRs) if the target is a public folder.
I want to write a Transport Agent (SMTPReceiveAgent, c#) to bypass this behavior. The goal is to change the NDR to a "normal" mail, that dont delete by exchange. I test some thinks around this and found no solution. Now i need help.
Here some questions:
It's easy to identify an NDR.
Content-Type: multipart/report;
report-type=delivery-status;
But what i have to change at the mail
to convert this to a "normal" mail? Change to multipart/alternative
not work or is not enough.
As an alternative i can create a new message with all infos captured
from the NDR. What is the best way to do this inside a
SMTReceiveAgents.OnSubmitted Event?
To create a copy from the public folder NDR for a normal user i tried args.Mailitem.Recipients.Add(new RoutingAddress("username#mydomain.com"))
in the EndOfDataHandler. This doesnt work. Why?
Any answers, hints or solutions?

Exchange 2007 NDRs to public Folders
Q1. You do have to change it to multipart/alternative, but also, you should find "Content-Type: message/delivery-status" and change it to text/plain but it isnt required
Q2. You could do this but your only options for the "original" message is to reject it to the sender, quarantine it or allow it..there is no delete / drop option.. but since it's going to a public folder it would be discarded.
If you go this route then enumerate the headers and body during EndOfHeadersEvent and then generate a new MailMessage object and include the headers and body from the original
Q3. That should work.. only reason that I can see it wouldnt work is if you're trying to send to an external recipient / domain that isn't an accepted domain on the server..if that is what youre trying to do then you'll need to create a mail contact with your real external address and then CC the NDR to the external contact
Below is the code I was able to accomplish what you're trying to do. The reason I hook into onRcpt and onEndOfHeaders is to check if the recipient entered is the public folder address..I found it faster than enumerating the rcpt list at the end of the header
void UserSendCounterSmtpReceiveAgent_OnRcptCommand(ReceiveCommandEventSource source, RcptCommandEventArgs e)
{
if(source == null || e == null)
{
return;
}
String recipient = e.RecipientAddress.ToString();
if (recipient.Equals("publicfolder#domain.com"))
{
this.testOnEndOfHeaders = true;
}
}
void UserSendCounterAgent_OnEndOfHeaders(ReceiveMessageEventSource source, EndOfHeadersEventArgs e)
{
if (source == null || e == null)
{
return;
}
if (testOnEndOfHeaders)
{
this.testOnEndOfHeaders = false;
Header obj = e.Headers.FindFirst("Content-Type");
if (obj.Value.Equals(#"multipart/report"))
{
obj.Value = #"multipart/alternative";
e.MailItem.Recipients.Add(new RoutingAddress("forwardto#domain.com"));
}
}
}

Related

Attempting to show message box and remove CC/To contents when writing new email

Hello and thank you in advance for any help you might be able to offer!
I am looking to create a C# add-in for Outlook that will take new emails that are being written and remove the contents of the To: and CC: fields if either exceeds a count of 10 recipients, then display a message box if either has exceeded 10. I've got some code already but I haven't worked with C# in about 7 years, so I'm very rusty and I feel like I might have done something wrong here. I'd greatly appreciate any insight on how to accomplish my goal.
The code:
private bool CheckRecipients(Outlook.MailItem mail)
{
bool retValue = false;
Outlook.Recipients recipients = null;
Outlook.Recipient recipientTo = null;
Outlook.Recipient recipientCC = null;
try
{
recipientTo = mail.recipientTo;
recipientCC = mail.recipientCC;
while(recipientTo.Count > 10)
{
recipientTo.Remove(1);
MessageBox.Show("You have added more than 10 recipients in the To: field. Please limit the To: field to 10 recipients.");
}
while(recipientCC.Count > 10)
{
recipientCC.Remove(1);
MessageBox.Show("You have added more than 10 recipients in the CC: field. Please limit the CC: field to 10 recipients.");
}
retValue = "something here";
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
finally
{
if (recipientCC != null) Marshal.ReleaseComObject(recipientCC);
if (recipientTo != null) Marshal.ReleaseComObject(recipientTo);
if (recipients != null) Marshal.ReleaseComObject(recipients);
}
return retValue;
}
After writing the code above, I ran the code as an add-in. Nothing happened.
There is no recipientTo and recipientCC properties in the Outlook object model:
recipientTo = mail.recipientTo;
recipientCC = mail.recipientCC;
Most probably the Recipients property is meant. It returns a Recipients collection that represents all the recipients for the Outlook item. Use Recipients(index) where index is the name or index number to return a single Recipient object. The name can be a string representing the display name, the alias, or the full SMTP email address of the recipient.
The Type property of a new Recipient object is set to the default for the associated AppointmentItem, JournalItem, MailItem, or TaskItem object and must be reset to indicate another recipient type. In case of MailItem recipient - one of the following OlMailRecipientType constants: olBCC, olCC, olOriginator, or olTo.
So, you can iterate over all recipients in the code where you could check the Type property and count recipients in each category if required.
You may find the article which I wrote for the technical blog helpful, see How To: Fill TO,CC and BCC fields in Outlook programmatically.
Another approach is to use a string based properties like To, Cc or Bcc that returns or sets a semicolon-delimited string list of display names for the recipients of the Outlook item. These properties contain the display names only, not the actual email addresses.
I am not sure if you can even compile your code - MailItem object does not implement recipientTo or recipientCC properties. It only exposes MailItem.Recipients collection. You can build your own To/CC lists by looping through the MailItem.Recipients collection and checking the Recipient.Type property (olTo/olCC/olBCC) for each recipient entry.

Moving Mail to Outlook Sent Folder. Mail is still editable

I am editing an email and send it to the recipient. But i also want to save the original mail in the sent folder. But if i move the mailobject to the folder the mail is still editable.
This is how i move the mail:
private void CopyMailToSent(Outlook.MailItem originalMail)
{
var folder = originalMail.SaveSentMessageFolder;
originalMail.Move(folder);
}
Can i set the mailobject to readonly or faking the send?
Firstly, Outlook Object Model would not let you set the MailItem.Sent property at all. On the MAPI level, the MSGFLAG_UNSENT bit in the PR_MESSAGE_FLAGS property can only be set before the message is saved for the very first time.
The only OOM workaround I am aware of is to create a post item (it is created in the sent state), set its message class to "IPM.Note", save it, release it, reopen by the entry id (it will be now MailItem in the sent state), reset the icon using PropertyAccessor, set some sender properties (OOM won't let you set all of them).
If using Redemption (I am its author) is an option, it will let you set the Sent property as well as the sender related properties, plus add recipients without having to resolve them.
Set MySession = CreateObject("Redemption.RDOSession")
MySession.MAPIOBJECT = Application.Session.MAPIOBJECT
Set folder = MySession.GetDefaultFolder(olFolderSentMail)
Set msg = folder.Items.Add("IPM.Note")
msg.Sent = True
msg.Recipients.AddEx "Joe The User", "joe#domain.demo", "SMTP", olTo
msg.Sender = MySession.CurrentUser
msg.SentOnBehalfOf = MySession.CurrentUser
msg.subject = "Test sent message"
msg.Body = "test body"
msg.UnRead = false
msg.SentOn = Now
msg.ReceivedTime = Now
msg.Save
I couldn't solve the problem but i did workaround which works fine for me.
I hope it's ok to post this here even it's not right the solution. If not, sorry i will delete it.
My workaround is saving the orignal mail as ".msg" file and then add it to the mail in the sent folder.
Then it looks like this:
This is the code:
private void SendMail(Outlook.Mailitem mail)
{
mail.SaveAs(tempDirectory + #"originalMail.msg");
var folder = mail.SaveSentMessageFolder;
ChangeMailSubject(mail);
ChangeMailText(mail);
mail.Send();
folder.Items.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler((sender) => AttachOriginalMail(sender);
}
private void AttachOriginalMail(object sender)
{
var mail = (Outlook.MailItem) sender;
mail.Attachments.Add(tempDirectory + #"originalMail.msg");
mail.Save();
}

How to get the MessageId from all exchange items

Hello I recently got into development around EWS. One of the issue came up to me is that a client ask me to import emails into database and he wants to detect the duplicate based on InternetMessageID this way he doesn't have to import the duplicate emails and my code came up to this point.
private static string GetInternetMessageID(Microsoft.Exchange.WebServices.Data.Item email)
{
EmailMessage emailMsg = email as EmailMessage;
string returnId = string.Empty;
if ((emailMsg != null)) {
try {
emailMsg.Load();
//loads additional info, without calling this ToRecipients (and more) is empty
} catch (ArgumentException ex) {
//retry
email.Load();
}
returnId = emailMsg.InternetMessageId;
} else {
//what to do?
}
return returnId;
}
I can handle regular emails, but for special exchange objects such as contact, Calendar, Posts etc it does not work because it could not cast it to an EmailMessage object.
And I know you can extract the internetMessageId from those objects. Because the client used to have another software that extract this ID for them, maybe the property is not called internetMessageID, I think I probally have to extract it from the internetMessageHeader. However when ever I try to get it from the item object it just throws me an error. How do I get the internet messageID from these "Special" exchange items?
PS i am aware of item.id.UniqueID however that is not what I want as this id changes if I move items from folder to another folder in exchange
Only objects that have been sent via the Transport service will have an InternetMessageId so things like Contacts and Tasks because they aren't messages and have never been routed via the Transport service will never have an Internet MessageId. You probably want to look at using a few properties to do this InternetMessageId can be useful for messages PidTagSearchKey https://msdn.microsoft.com/en-us/library/office/cc815908.aspx is one that can be used (if you good this there are various examples of using this property).
If your going to use it in Code don't use the method your using to load the property on each item this is very inefficient as it will make a separate call for each object. Because these I'd's are under 256 Kb just retrieve then when using FindItems. eg
ExtendedPropertyDefinition PidTagSearchKey = new ExtendedPropertyDefinition(0x300B, MapiPropertyType.Binary);
ExtendedPropertyDefinition PidTagInternetMessageId = new ExtendedPropertyDefinition(0x1035, MapiPropertyType.String);
PropertySet psPropSet = new PropertySet(BasePropertySet.IdOnly);
psPropSet.Add(PidTagSearchKey);
psPropSet.Add(PidTagInternetMessageId);
ItemView ItemVeiwSet = new ItemView(1000);
ItemVeiwSet.PropertySet = psPropSet;
FindItemsResults<Item> fiRess = null;
do
{
fiRess = service.FindItems(WellKnownFolderName.Inbox, ItemVeiwSet);
foreach (Item itItem in fiRess)
{
Object SearchKeyVal = null;
if (itItem.TryGetProperty(PidTagSearchKey, out SearchKeyVal))
{
Console.WriteLine(BitConverter.ToString((Byte[])SearchKeyVal));
}
Object InternetMessageIdVal = null;
if (itItem.TryGetProperty(PidTagInternetMessageId, out InternetMessageIdVal))
{
Console.WriteLine(InternetMessageIdVal);
}
}
ItemVeiwSet.Offset += fiRess.Items.Count;
} while (fiRess.MoreAvailable);
If you need larger properties like the Body using the LoadPropertiesForItems Method https://blogs.msdn.microsoft.com/exchangedev/2010/03/16/loading-properties-for-multiple-items-with-one-call-to-exchange-web-services/

C# Get Values from two different websites

I am using HTMLElementCollection, HtmlElement to iterate through a website and using Get/Set attributes of a website HTML and returning it to a ListView. Is it possible to get values from website a and website b to return it to the ListView?
HtmlElementCollection oCol1 = oDoc.Body.GetElementsByTagName("input");
foreach (HtmlElement oElement in oCol1)
{
if (oElement.GetAttribute("id").ToString() == "search")
{
oElement.SetAttribute("value", m_sPartNbr);
}
if (oElement.GetAttribute("id").ToString() == "submit")
{
oElement.InvokeMember("click");
}
}
HtmlElementCollection oCol1 = oDoc.Body.GetElementsByTagName("tr");
foreach (HtmlElement oElement1 in oCol1)
{
if (oElement1.GetAttribute("data-mpn").ToString() == m_sPartNbr.ToUpper())
{
HtmlElementCollection oCol2 = oElement1.GetElementsByTagName("td");
foreach (HtmlElement oElement2 in oCol2)
{
if (oElement2 != null)
{
if (oElement2.InnerText != null)
{
if (oElement2.InnerText.StartsWith("$"))
{
string sPrice = oElement2.InnerText.Replace("$", "").Trim();
double dblPrice = double.Parse(sPrice);
if (dblPrice > 0)
m_dblPrices.Add(dblPrice);
}
}
}
}
}
}
As one of the comments mentioned the better approach would be to use HttpWebRequest to send a get request to www.bestbuy.com or whatever site. What it returns is the full HTML code (what you see) which you can then parse through. This kind of approach keeps you from seinding too many requests and getting blacklisted. If you need to click a button or type in a text field its best to mimic human input to avoid being blacklisted also. I would suggest injecting a simple javascript into the page header or body and execute it from the app to send a 'onClick' event from the button (which would then reply with a new page to parse or display) or to modify the text property of something.
this example is in c++/cx but it originally came from a c# example. the script sets the username and password text fields then clicks the login button:
String^ script = "document.GetElementById('username-text').value='myUserName';document.getElementById('password-txt').value='myPassword';document.getElementById('btn-go').click();";
auto args = ref new Platform::Collections::Vector<Platform::String^>();
args->Append(script);
create_task(wv->InvokeScriptAsync("eval", args)).then([this](Platform::String^ response){
//LOGIN COMPLETE
});
//notes: wv = webview
EDIT:
as pointed out the absolute best approach would be to get/request an api. I was surprised to see that site mason pointed out for bestbuy developers. Personally I have only tried to work with auto part stores who either laugh while saying I can't afford it or have no idea what I'm asking for and hang up (when calling corporate).
EDIT 2: in my code the site used was autozone. I had to use chrome developer tools (f12) to get the names of the username, password, and button name. From the developer tools you can also watch what is sent from your computer to the site/server. This allows you to recreate everything and mimic javascript input and actions using post/get with HttpWebRequest.

Send all email data to a service from an outlook addin

I'm new to office addins. I'm an MVC programmer but this project has been dumped on me as no one else wants to do it. I need to create an outlook addin that will forward all email data to a service where communications can be tracked by a recruitment system.
I am using
Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(saveEmail);
where I then cast the email into a Outlook.MailItem. The problem is I see no way of getting the from and to email addresses. All it gives me is the name of the people. Am I missing something?
So far the best solution I can think of is to save the msg as a .msg file. Forward that to my service and then user a parser I found to convert it to HTML.
Any suggestions?
To access the recipients, loop through MailItem.Recipients collection and access Recipient.Name and Recipient.Address properties.
Sender related properties are not yet set by the time ItemSend event fires - the earliest you can access sender properties is when Items.ItemAdd event fires on the Sent Items folder (retrieve it using Namespace.GetDefaultFolder).
You can read the MailItem.SendUsingAccount. If it is null, use the first Account from the Namespace.Acounts collection. You can then use Account.Recipient object.
Keep in mind that you should not blindly cast outgoing items to MailItem objects - you can also have MeetingItem and TaskRequestItem objects.
OK using the info given to me by Dmitry Streblechenko and some other info I just looked up here is my solution so far.
In the ItemSend event I first make sure that sent email is moved to the default sent items folder. I'm testing outlook using gmail so normally these will go elsewhere. sentMailItems is made as a class field as apparently it will get garbage collected if its just declared inside the Startup function (Something quite odd to me an MVC programmer :) ).
I' will test this on exchange when I get back to office an hopefully all goes well.
public partial class ThisAddIn
{
public Outlook.Items sentMailItems;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(ItemSend);
sentMailItems = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail).Items;
sentMailItems.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler(Items_ItemAdd);
}
void Items_ItemAdd(object item)
{
MessageBox.Show(((Outlook.MailItem)item).Subject);
var msg = Item as Outlook.MailItem;
string from = msg.SenderEmailAddress;
string allRecip = "";
foreach (Outlook.Recipient recip in msg.Recipients)
{
allRecip += "," + recip.Address;
}
}
private void ItemSend(object Item, ref bool Cancel)
{
if (!(Item is Outlook.MailItem))
return;
var msg = Item as Outlook.MailItem;
msg.DeleteAfterSubmit = false; // force storage to sent items folder (ignore user options)
Outlook.Folder sentFolder = this.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail) as Outlook.Folder;
if (sentFolder != null)
msg.SaveSentMessageFolder = sentFolder; // override the default sent items location
msg.Save();
}
//Other auto gen code here....
}

Categories