none
Copy MailItem.Recipients to a new mail like this? RRS feed

  • Question

  • Is this the most reliable way of copy a recipient list from within ItemSend from the email that is being sent to a new email?

    I am a little afraid of relying on recipient.Name as the only binding variable as there can be many recipient types (GAL recipient, Outlook contact, SMTP address, distribution list, etc).

    foreach (Outlook.Recipient recipient in mail.Recipients)
    {
    Outlook.Recipient newRecipient = newMail.Recipients.Add(recipient.Name);
    newRecipient.Type = recipient.Type;
    newRecipient.Resolve();
    }


    Saturday, March 26, 2016 5:10 PM

Answers

  • Hi Dingoshark,

    We can use Recipients.Add(Name) Method to add a recipient for the mail item. And the name of the recipient, it can be a string representing the display name, the alias, or the full SMTP e-mail address of the recipient.

    So we can get the SMTP e-mail address to create the recipient for the new mail item. Here is an example that go through the Recipients of mail item and retrieve the SMTP e-mail address then create the new recipient and add them into the new mail item:

    Sub CreateMail()
    Dim newMessage As MailItem
    Set newMessage = Application.CreateItem(olMailItem)
    
    Dim rec As Recipient
    Dim recNew As Recipient
    
    
    For Each rec In Application.ActiveExplorer.Selection.Item(1).Recipients
            newMessage.Recipients.Add (GetSMTPAddress(rec.addressEntry))
    
    Next
    
    newMessage.Recipients.ResolveAll
    newMessage.Display
    
    End Sub
    
    Function GetSMTPAddress(addressEntry)
    
    
       If addressEntry.AddressEntryUserType = OlAddressEntryUserType.olExchangeUserAddressEntry Or addressEntry.AddressEntryUserType = OlAddressEntryUserType.olExchangeRemoteUserAddressEntry Then
        
        'Use the ExchangeUser object PrimarySMTPAddress
        Dim exchUser As ExchangeUser
        Set exchUser = addressEntry.GetExchangeUser()
                    If Not exchUser Is Nothing Then
                    
                        GetSMTPAddress = exchUser.PrimarySmtpAddress
                    
                    Else
                        GetSMTPAddress = Nothing
                    End If
       ElseIf InStr(addressEntry, "@") <> 0 Then
             GetSMTPAddress = addressEntry.Address
       Else
       
       
            GetSMTPAddress = addressEntry.PropertyAccessor.GetProperty(PR_SMTP_ADDRESS)
       End If
            
    End Function

    Hope it is helpful.

    Regards & Fei


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Friday, April 1, 2016 10:00 AM
    Moderator

All replies


  • You can copy one property at a time using Recipient.PropertyAccessor.GetProperty/SetProperty. Take a look at all the recipient properties using OutlookSpy (click IMessage button go to the GetRecipientTable tab).


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Saturday, March 26, 2016 5:22 PM
  • But the properties themselves (not their values) are typically different for different recipient types (at least for MAPI), so I would then need to start copying all properties that are available. Somehow this feels a bit fishy.
    Saturday, March 26, 2016 5:31 PM
  • I came up with a better solution. Simply call MailItem.Copy() and then modify the email body/subject/format. Then I get the same Recipients list and there is no sketchy recipient resolving based on name done. This at least feels much more reliable.
    • Edited by Dingoshark Saturday, March 26, 2016 5:41 PM
    • Marked as answer by Dingoshark Saturday, March 26, 2016 6:07 PM
    • Unmarked as answer by Dingoshark Saturday, March 26, 2016 7:40 PM
    Saturday, March 26, 2016 5:41 PM
  • In most cases, the following properties will be sufficient:

    PR_ADDRTYPE_W
    PR_DISPLAY_NAME_W
    PR_DISPLAY_TYPE
    PR_DISPLAY_TYPE_EX
    PR_EMAIL_ADDRESS_W
    PR_ENTRYID
    PR_OBJECT_TYPE
    PR_RECIPIENT_ENTRYID
    PR_RECIPIENT_FLAGS
    PR_RECIPIENT_TRACKSTATUS
    PR_RECIPIENT_TYPE
    PR_RECORD_KEY
    PR_RESPONSIBILITY
    PR_SEARCH_KEY
    PR_SEND_RICH_INFO
    PR_SMTP_ADDRESS_W
    PR_TRANSMITABLE_DISPLAY_NAME_W


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Saturday, March 26, 2016 5:44 PM
  • Then if you copy the HTML or RTF body, you will need to make sure that the attachments and their properties are copied as well.

    Plus you will not be able to replace the message being sent and passed to you in the ItemSend event handler, you can only create a new one.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Saturday, March 26, 2016 5:46 PM
  • After looking at this closer, it seems like mailItem.Copy() throws an exception when it is called from within ItemSend if the email is in the Drafts folder or the Sent folder. This makes is unusable so I might have to go with copying over all those properties but it does not seem like a reliable solution (it needs to work in all edge cases). It would be much better if I could get MailItem.Copy() to work.
    Saturday, March 26, 2016 7:42 PM
  • Are you copying the item being sent? Are you trying to make a copy of it? Or are you copying the recipients from some other item?

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Saturday, March 26, 2016 8:06 PM
  • I am calling MailItem.Copy() on the mail that is being sent in the ItemSend event. The only reason I am doing it is to copy the recipients.
    Saturday, March 26, 2016 8:09 PM
  • But copy them where? To some other email? What do you do with that email?

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Saturday, March 26, 2016 8:29 PM
  • Yes, to another mail. I am generating a password email after the attachments have been zipped and encrypted of the original email. I then intend to send out the password email to the same recipients as the original email.
    Saturday, March 26, 2016 9:37 PM
  • But you only need the same recipients, right? The body will be different. For that purpose copying the recipient properties below will be sufficient. Make sure to ignore exceptions raised by PropertyAccessor.GetProperty when a particular property is not present.

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Saturday, March 26, 2016 9:57 PM
  • Yes, correct. Body will be different and new email attachment format will be set to Plain. And new email attachments will be none. So you are suggesting I call newRecipient.SetProperty(..., oldRecipient.GetProperty(...)) for each of the properties you listed, right? 

    Just to confirm, the initial method I suggested with adding recipients from Name() and then calling Resolve() does not work because?

    Saturday, March 26, 2016 10:41 PM
  • Yes, just warp each line that calls SetProperty / GetProperty into a try / catch block.

    Resolve might not work because

    1. Name can be ambiguous

    2. Name can be not in an address book at all (e.g. a reply).

    3. Address will not work because you will lose the name and its type can be other than SMTP (e.g. EX type address).


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!


    Sunday, March 27, 2016 12:42 AM
  • Hello Dingoshark,

    Making a copy of the original item is the most reliable way I believe. There is no need to copy multiple low-level properties in that case. You just need to make adjustments according to your business logic.

    Instead of doing such operations in the ItemSend event handler you can repurpose ribbon controls where you can copy the item and make any customizations. See Temporarily Repurpose Commands on the Office Fluent Ribbon for more information.

    Also you may consider handling the Send event of the MailItem class which is fired when the user selects the Send action for an item, or when the Send method is called for the item.

    Sunday, March 27, 2016 12:43 PM
  • Thank you. If you know of any suggestions how to test edge cases, or if you have any comments on the code, it would be very valuable. This is the code:

    Outlook.MailItem newMail = (Outlook.MailItem) Addin._outlookApplication.CreateItem(OlItemType.olMailItem);

    foreach (Outlook.Recipient recipient in mail.Recipients)
    {
    Outlook.Recipient newRecipient = newMail.Recipients.Add(recipient.Name);
    newRecipient.Type = recipient.Type;
    CopyProperties(recipient, newRecipient);

    try
    {
    newRecipient.Resolve();
    }
    catch (Exception e)
    {
    // ignore this error
    }
    }

    private static void CopyProperties(Outlook.Recipient src, Outlook.Recipient dst)
    {
    // XXXXYYYY => XXXX = Property def and YYYY = data type
    // find them here: http://www.dimastr.com/redemption/enum_MAPITags.htm
    const string PR_ENTRYID = "http://schemas.microsoft.com/mapi/proptag/0x0FFF0102";
    const string PR_DISPLAY_NAME_W = "http://schemas.microsoft.com/mapi/proptag/0x3001001F";
    const string PR_EMAIL_ADDRESS_W = "http://schemas.microsoft.com/mapi/proptag/0x3003001F";
    const string PR_SEARCH_KEY = "http://schemas.microsoft.com/mapi/proptag/0x300B0102";
    const string PR_SMTP_ADDRESS_W = "http://schemas.microsoft.com/mapi/proptag/0x39FE001F";
    const string PR_ADDRTYPE_W = "http://schemas.microsoft.com/mapi/proptag/0x3002001F";
    const string PR_DISPLAY_TYPE = "http://schemas.microsoft.com/mapi/proptag/0x39000003";
    const string PR_DISPLAY_TYPE_EX = "http://schemas.microsoft.com/mapi/proptag/0x39050003";
    const string PR_OBJECT_TYPE = "http://schemas.microsoft.com/mapi/proptag/0x0FFE0003";
    const string PR_RECIPIENT_ENTRYID = "http://schemas.microsoft.com/mapi/proptag/0x5FF70102";
    const string PR_RECIPIENT_FLAGS = "http://schemas.microsoft.com/mapi/proptag/0x5FFD0003";
    const string PR_RECIPIENT_TRACKSTATUS = "http://schemas.microsoft.com/mapi/proptag/0x5FFF0003";
    const string PR_RECIPIENT_TYPE = "http://schemas.microsoft.com/mapi/proptag/0x0C150003";
    const string PR_RECORD_KEY = "http://schemas.microsoft.com/mapi/proptag/0x0FF90102";
    const string PR_RESPONSIBILITY = "http://schemas.microsoft.com/mapi/proptag/0x0E0F000B";
    const string PR_SEND_RICH_INFO = "http://schemas.microsoft.com/mapi/proptag/0x3A40000B";
    const string PR_TRANSMITABLE_DISPLAY_NAME_W = "http://schemas.microsoft.com/mapi/proptag/0x3A20001F";

    CopyProperty(src, dst, PR_ENTRYID);
    CopyProperty(src, dst, PR_DISPLAY_NAME_W);
    CopyProperty(src, dst, PR_EMAIL_ADDRESS_W);
    CopyProperty(src, dst, PR_SEARCH_KEY);
    CopyProperty(src, dst, PR_SMTP_ADDRESS_W);
    CopyProperty(src, dst, PR_ADDRTYPE_W);
    CopyProperty(src, dst, PR_DISPLAY_TYPE);
    CopyProperty(src, dst, PR_DISPLAY_TYPE_EX);
    CopyProperty(src, dst, PR_OBJECT_TYPE);
    CopyProperty(src, dst, PR_RECIPIENT_ENTRYID);
    CopyProperty(src, dst, PR_RECIPIENT_FLAGS);
    CopyProperty(src, dst, PR_RECIPIENT_TRACKSTATUS);
    CopyProperty(src, dst, PR_RECIPIENT_TYPE);
    CopyProperty(src, dst, PR_RECORD_KEY);
    CopyProperty(src, dst, PR_RESPONSIBILITY);
    CopyProperty(src, dst, PR_SEND_RICH_INFO);
    CopyProperty(src, dst, PR_TRANSMITABLE_DISPLAY_NAME_W);
    }

    private static void CopyProperty(Outlook.Recipient src, Outlook.Recipient dst, string schema)
    {
    try
    {
    object obj = src.PropertyAccessor.GetProperty(schema);
    dst.PropertyAccessor.SetProperty(schema, obj);
    }
    catch (Exception)
    {
    // all properties do not exist for all recipients
    }
    }


    Sunday, March 27, 2016 12:51 PM
  • Hello Dingoshark,

    Making a copy of the original item is the most reliable way I believe. There is no need to copy multiple low-level properties in that case. You just need to make adjustments according to your business logic.

    Instead of doing such operations in the ItemSend event handler you can repurpose ribbon controls where you can copy the item and make any customizations. See Temporarily Repurpose Commands on the Office Fluent Ribbon for more information.

    Also you may consider handling the Send event of the MailItem class which is fired when the user selects the Send action for an item, or when the Send method is called for the item.

    That is nice in theory and is also what I initially thought, but how do I do a copy when MailItem.Copy() is crashing for me if I do it on an item in Drafts or Sent folders (like I mentioned below)?

    I need to do this logic from ItemSend so Ribbon buttons are not an option.


    • Edited by Dingoshark Sunday, March 27, 2016 3:18 PM
    Sunday, March 27, 2016 3:06 PM
  • That looks fine to me.

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Sunday, March 27, 2016 5:03 PM
  • Regarding the code. I tried letting srcRecipient.Name fail in though and instead just do Recipients.Add( "dummy@dummy.com") in hope that the properties would copy over that dummy address. They did not do that however, and newRecipient.Address was still dummy@dummy.com and the email went out to dummy@dummy.com.
    Sunday, March 27, 2016 5:18 PM
  • Does SetProperty raise an exception for any property?


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Sunday, March 27, 2016 5:54 PM
  • Not from what I can see. Did you try your solution yourself or was it a guess?
    • Edited by Dingoshark Sunday, March 27, 2016 6:02 PM
    Sunday, March 27, 2016 5:59 PM
  • VS by default does not show exception if they are handled. Try to turn all exceptions on (Ctrl + D | E).

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Sunday, March 27, 2016 6:08 PM
  • These two did not throw an exception, the rest did. I think your solution does not work at all unfortunately.

    CopyProperty(src, dst, PR_RECORD_KEY);

    CopyProperty(src, dst, PR_SEND_RICH_INFO);

    Sunday, March 27, 2016 6:15 PM
  • What was the exception? OOM sometimes like to play the big brother...

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Sunday, March 27, 2016 6:31 PM
  • + InnerException {"Exception has been thrown by the target of an invocation."}

    System.Exception {System.Reflection.TargetInvocationException}

    It fails on the second line when trying to set the property:

    object obj = src.PropertyAccessor.GetProperty(schema);
    dst.PropertyAccessor.SetProperty(schema, obj);

    • Edited by Dingoshark Sunday, March 27, 2016 6:49 PM
    Sunday, March 27, 2016 6:48 PM
  • Do you see an inner exception? If OOM decides that it has to prevent you from shooting yourself in the foot, there is not much you can do...

    If using Redemption is an option, it allows to either pass Recipient as an argument to Recipients.Add or set the Recipients property wholesale.

    mail.Save();
    Redemption.RDOSession session = new Redemption.RDOSession();
    Redemption.RDOMail rmail = (Redemption.RDOMail)session.GetRDOObjectFromOutlookObject(mail);
    Redemption.RDOMail rNewMail = session.GetDefaultFolder(rdoDefaultFolders.olFolderDrafts).Items.Add("IPM.Note");
    rNewMail.Recipients = rmail.Recipients;
    rNewMail.Save();
    //you can now reopen the new message in OOM using
    //Namespace.GetItemFromID

     

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Sunday, March 27, 2016 8:11 PM
  • The inner exception is "Exception has been thrown by the target of an invocation.".

    I am not sure I can follow you 100%, but it seems like you are saying that with Redemption and the code you have illustrated above, it is fully possible to copy the Recipients. Is that correct? If so, can I trial this if it works?

    An alternative solution that I have not tried yet, is to trigger the Add-in code when the original email ends up in the Sent folder, and then do the Copy() and send out the modified email to the same recipients. Maybe it works better to do the Copy() from there instead of from inside the ItemSend() event.
    • Edited by Dingoshark Sunday, March 27, 2016 8:37 PM
    Sunday, March 27, 2016 8:22 PM
  • Yes, Redemption will let you copy the recipients, either one property, one recipient, or the whole collection at a time. The code above opens the message as an RDOMail object, creates new message, then copies the recipients.  

    After the message is saved, you can reopen the message in OOM and use your existing code. Redemption can be downloaded from http://www.dimastr.com/redemption/download.htm


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Sunday, March 27, 2016 8:56 PM
  • Is there any drawback in using Redemption in a commercial add-in? I do absolutely not want to registry the Redemption DLL at the target host, but I prefer to just load it like a regular DLL. That seems to be possible though: http://www.dimastr.com/redemption/security.htm#redemptionloader
    Sunday, March 27, 2016 10:35 PM
  • Yes, RedemptionLoader will work - you just need copy it alongside your dll.

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Sunday, March 27, 2016 10:58 PM
  • I have now tried a bit but it crashes on the line "Redemption.RDOMail rmail = (Redemption.RDOMail) session.GetRDOObjectFromOutlookObject(mail);". I suspect that the session is not valid because I see Session.Identity = null in the debugger. Do I need to login differently? Or can I copy session information from the regular session I am in? Remember I am inside the Send event for an email for the code below.

    mail.Save();
    Redemption.RDOSession session = new Redemption.RDOSession();
    session.Logon();
    Redemption.RDOMail rmail = (Redemption.RDOMail) session.GetRDOObjectFromOutlookObject(mail);
    Redemption.RDOMail rNewMail = session.GetDefaultFolder(Redemption.rdoDefaultFolders.olFolderDrafts).Items.Add("IPM.Note");
    rNewMail.Recipients = rmail.Recipients;
    rNewMail.Save();
    string entryId = rNewMail.EntryID;
    Outlook.MailItem newMail = (Outlook.MailItem) Addin._outlookApplication.Session.GetItemFromID(entryId);



    • Edited by Dingoshark Sunday, March 27, 2016 11:06 PM
    Sunday, March 27, 2016 11:05 PM
  • Do you mean crash as in "COM exception"? What is it?

    Do not call RDOSession.Logon - set RDOSession.MAPIOBJECT to Application.Session.MAPIOBJECT from Outlook. GetRDOObjectFromOutlookObject will do that for you anyway.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Sunday, March 27, 2016 11:10 PM
  • Ok, I removed the "session.Logon()" line. Otherwise, exact as above. It still crashes on the first "rmail line" with error "{"Object reference not set to an instance of an object"}. 
    Sunday, March 27, 2016 11:15 PM
  • That means session is null. Did you register the dll with regsvr32.exe? What is the Outlook bitness?

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Sunday, March 27, 2016 11:29 PM
  • I am running Outlook 2016 32-bit (100% sure). I first installed using the install.exe. The tried regsrv32 just to be 100% sure and it said DLL successfully registered.
    Sunday, March 27, 2016 11:37 PM
  • What exactly do you mean by Session.Identity being null? RDOSession does not have a property named Identity.

    Can you run the following script from Outlook VBA editor or OutlookSpy (click Script, paste the script below, click Run):

    set rSession = CreateObject("Redemption.RDOSession")
    rSession.MAPIOBJECT = Application.Session.MAPIOBJECT
    MsgBox rSession.Version


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Monday, March 28, 2016 4:25 AM
  • Ran it from OutlookSpy and it showed "5.10.0.4312".
    Monday, March 28, 2016 9:48 AM
  • So Redemption is installed and working, right? When debugging your addin, are yo using VS hosting process?

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Monday, March 28, 2016 1:23 PM
  • Hi Dingoshark,

    We can use Recipients.Add(Name) Method to add a recipient for the mail item. And the name of the recipient, it can be a string representing the display name, the alias, or the full SMTP e-mail address of the recipient.

    So we can get the SMTP e-mail address to create the recipient for the new mail item. Here is an example that go through the Recipients of mail item and retrieve the SMTP e-mail address then create the new recipient and add them into the new mail item:

    Sub CreateMail()
    Dim newMessage As MailItem
    Set newMessage = Application.CreateItem(olMailItem)
    
    Dim rec As Recipient
    Dim recNew As Recipient
    
    
    For Each rec In Application.ActiveExplorer.Selection.Item(1).Recipients
            newMessage.Recipients.Add (GetSMTPAddress(rec.addressEntry))
    
    Next
    
    newMessage.Recipients.ResolveAll
    newMessage.Display
    
    End Sub
    
    Function GetSMTPAddress(addressEntry)
    
    
       If addressEntry.AddressEntryUserType = OlAddressEntryUserType.olExchangeUserAddressEntry Or addressEntry.AddressEntryUserType = OlAddressEntryUserType.olExchangeRemoteUserAddressEntry Then
        
        'Use the ExchangeUser object PrimarySMTPAddress
        Dim exchUser As ExchangeUser
        Set exchUser = addressEntry.GetExchangeUser()
                    If Not exchUser Is Nothing Then
                    
                        GetSMTPAddress = exchUser.PrimarySmtpAddress
                    
                    Else
                        GetSMTPAddress = Nothing
                    End If
       ElseIf InStr(addressEntry, "@") <> 0 Then
             GetSMTPAddress = addressEntry.Address
       Else
       
       
            GetSMTPAddress = addressEntry.PropertyAccessor.GetProperty(PR_SMTP_ADDRESS)
       End If
            
    End Function

    Hope it is helpful.

    Regards & Fei


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Friday, April 1, 2016 10:00 AM
    Moderator