Frequently Asked Questions

Troubleshooting notes for common Redemption deployment, threading, message submission, and Outlook compatibility issues.

Some entries are compatibility notes for older Outlook versions. They remain here because older deployments and archived support cases still link to these anchors.

CO_E_WRONGOSFORAPP when creating a Redemption object

This error usually means the bitness of your application, Redemption, and Outlook/MAPI do not match. Redemption is an in-process COM library, so its bitness must match the parent process. Redemption also loads the MAPI DLLs in process, so the MAPI system bitness must match as well.

If you compile .NET code as Any CPU, it runs with the bitness of the host OS. That can be wrong when 32-bit Outlook is installed on 64-bit Windows. In that case, build and install the 32-bit version of your application. The only common case where Any CPU makes sense is an Outlook COM add-in, because Outlook determines the add-in process bitness when it loads it.

If your application is 32-bit and 64-bit Outlook is installed, the standalone process cannot load Outlook's 64-bit MAPI stack. Recompile the application as 64-bit or create Redemption objects inside the Outlook process.

Outlook bitness can be detected from the Bitness string value under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\16.0\Outlook. For Click-to-Run installations, check HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Microsoft\Office\16.0\Outlook.

One workaround for a standalone application is to create the Redemption object inside outlook.exe through Application.CreateObject. The object is then guaranteed to match Outlook/MAPI bitness, and COM marshals calls back to your process. The tradeoff is that calls are marshaled through Outlook's main thread, which can make lengthy operations impractical.

'start Outlook if it is not running
set app = CreateObject("Outlook.Application")
set ns = app.GetNamespace("MAPI")
ns.Logon

'Create RDOSession object in the Outlook process address space
'Redemption bitness is guaranteed to match Outlook
set Session = app.CreateObject("Redemption.RDOSession")

'use the same MAPI session used by Outlook
Session.MAPIOBJECT = ns.MAPIOBJECT

'now test if it works - the call will be marshaled to Redemption object
'running inside Outlook process
MsgBox "There are " & Session.Stores.Count & " stores in the profile named '" & Session.ProfileName & "'"

Using Redemption objects on multiple threads

You can use Redemption, and MAPI in general, on multiple threads. The rule is that MAPI must be initialized on every thread where a MAPI object is used. Every creatable Redemption object, such as RDOSession, initializes MAPI by calling MAPIInitialize when it is created.

MAPI considers the first thread in the process to call MAPIInitialize the main thread. MAPI allocates objects with thread affinity, such as hidden notification windows, on that thread and expects it to remain alive until all other MAPI-using threads exit.

Create an RDOSession object, or another creatable Redemption object such as SafeMailItem, on the main thread and keep it alive for the lifetime of the application before creating Redemption objects on secondary threads.

To share the same MAPI session across threads, log on on the main thread, then set the secondary thread's RDOSession.MAPIOBJECT property to the main thread's RDOSession.MAPIOBJECT.

MAPI_E_FAILONEPROVIDER from RDOSession.LogonExchangeMailbox

This generally happens when you attempt to open a mailbox owned by a domain user different from the current process user. You can also get WSAECONNRESET if the connection is actively refused, for example when a firewall blocks the connection or the process runs under a user context that cannot access network resources.

Exchange is strict about the current user context. Prefer calling RDOSession.LogonExchangeMailbox once for the current user, then use RDOSession.GetSharedMailbox to open another user's mailbox.

Outlook 2013 Click-to-Run MAPI marshalling errors

If Outlook 2013 Click-to-Run raises "interface not supported" errors when you set RDOSession.MAPIOBJECT, Safe*Item.Item, or MAPITable.Item, install the Microsoft-provided fix from Outlook_2013_C2R_fix.ZIP.

The fix restores missing MAPI marshalling registry keys. It is needed when your code requires MAPI object marshalling, for example when it runs in an executable other than outlook.exe and uses the Safe*Item family or sets RDOSession.MAPIOBJECT from Outlook's Namespace.MAPIOBJECT.

IMAP message retrieval hangs in OnNewMail

When MAPI fires the new mail event, it does so on a thread different from the one where RDOSession was created. Redemption switches to the main thread by calling SendMessage() on a window handle created on the main thread.

If you open a message through GetMessageFromID during that notification, the IMAP4 provider may try to retrieve data from the IMAP server on a separate thread. When it switches threads while the new mail notification is still blocked, the call can hang.

Store the entry ID and use a timer instead. In .NET WinForms, use the Forms Timer class so the event fires on the main thread when the application is idle. Do not use async/await for this path because it can continue on a thread where MAPI has not been initialized.

Another option is to bypass the IMAP4 layer and work with the underlying PST store by unwrapping the IMAP4 store.

RDOStore unwrappedStore = RDOSession.Stores.UnwrapStore(YourIMAP4Store);
RDOMail msg = unwrappedStore.GetMessageFromID(messageEntryId);

Most likely the retrieved item will only be an envelope without the body and attachments.

Sent message stays in Drafts

Message submission is a two-step process in Extended MAPI:

  1. Call IMessage::Submit().
  2. Flush the outgoing message queue.

With Exchange Server, step 2 is not required because the Exchange message store is tightly bound to the Exchange transport provider. With POP3/SMTP transport and a PST store, step 2 is required. To flush the message queue, create a Redemption.MAPIUtils object and call DeliverNow after SafeMailItem.Send.

MailItem.Send

Set Utils = CreateObject("Redemption.MAPIUtils")
Utils.DeliverNow

There is no reliable Extended MAPI way to flush queues in Outlook 2002 or newer with a PST file and POP3/SMTP transport, Outlook 2000 Internet Only Mode, or Outlook 2003+ Exchange Cached Mode. For cached Exchange mode, disable cached mode to force online mode if you need immediate delivery.

As a last resort, simulate the Outlook Send/Receive command after sending the message.

MailItem.Send

Set Btn = Application.ActiveExplorer.CommandBars.FindControl(1, 5488)
Btn.Execute

In Outlook 2003, the button became a dropdown. The actual Send/Receive command is a child command:

Set Btn = Application.ActiveExplorer.CommandBars.FindControl(1, 7095)
Btn.Execute

The code above assumes there is an active Explorer. If Outlook was started programmatically and no folder is displayed, use the Namespace.SyncObjects collection:

set NS = Application.GetNamespace("MAPI")
NS.Logon
Set Sync = NS.SyncObjects.Item(1)
Sync.Start

In Outlook 2010 and newer, you can also use Namespace.SendAndReceive.

SafeCurrentUser is empty with Outlook 2002 POP3/PST legacy

Outlook 2002 broke the Extended MAPI area used to retrieve the current user identity when using a POP3/SMTP transport provider and a PST message store. Microsoft knew about this problem, but no workaround was provided.

Outlook property changes are empty or different in Redemption

When you set a property in Outlook, the value is not immediately saved until you explicitly call Save or use the Outlook UI to save the item. Until then, Outlook Object Model can see the changed value, but Extended MAPI, and therefore Redemption, cannot.

Call Save on the Outlook item before reading the property through Redemption.

Reading many messages is slower than Outlook Object Model

Outlook optimizes access to many message properties when you read them from an Outlook folder. Redemption does not have access to that cache and must query Extended MAPI for most properties.

If you need to read many messages, limit the set first. For example, do not loop through every item if Outlook can filter by sender:

Set Items = MAPIFolder.Items
Set Msg = Items.Find("[SenderName] = 'John Smith'")

Do While not (Msg is Nothing)
  ' now you can access the message
  Debug.Print Msg.Subject
  Set Msg = Items.FindNext
Loop

An even better approach is to use MAPI Tables.

Outlook stays in memory after it is closed

Some Redemption objects, such as MAPIUtils or AddressEntry, need an Extended MAPI session. For performance, the session is cached for later use by the same or other Redemption objects.

Because of the redesigned Extended MAPI support in Outlook 2002, Outlook might not close correctly while there is an outstanding session reference. Call MAPIUtils.Cleanup, AddressEntry.Cleanup, or SafeCurrentUser.Cleanup to make Redemption release the Extended MAPI session.

SafeMailItem.Item crashes with Visual Studio .NET beta 2 legacy

This was typically caused by Visual Studio .NET beta 2 replacing system libraries used for dynamic type-info support. Outlook appeared to work normally, but Redemption relied on that functionality. The fix was to uninstall the beta or upgrade to a later Visual Studio version.

MAPI functions are missing when Outlook is not the default mail client

If Outlook is not the default mail client, MAPI calls from a particular DLL or executable may need to be explicitly routed to the system MAPI DLL. Register your executable under the MSMapiApps registry key.

In short, list the executable or DLL as a string value under HKLM\Software\Microsoft\Windows Messaging Subsystem\MSMapiApps. An empty value routes the call directly to Mapi32x.dll. A non-empty value points to a mail client key under HKLM\Software\Clients\Mail.

HKLM\Software\Microsoft\Windows Messaging Subsystem\MSMapiApps::exchng32.exe = ""
  (route call directly to Mapi32x.dll)

HKLM\Software\Microsoft\Windows Messaging Subsystem\MSMapiApps::msspc32.dll = "Microsoft Outlook"
  (route call using Microsoft Outlook key under HKLM\Software\Clients\Mail)

Imported or copied messages show as unsent

At the MAPI level, some message flags are writable only before the first save and read-only after that. For the sent/unsent flag, MSGFLAG_UNSENT in PR_MESSAGE_FLAGS, MAPI cannot override the flag after the item is saved.

One workaround is to create the message in the sent state. Instead of creating a mail item, create a post item, assign it to Safe*Item.Item, then call Safe*Item.Import() or CopyTo().

PR_ICON_INDEX = &H10800003

set Item = Application.CreateItem(olPostItem)  'create a Post item instead of a regular unsent message
Item.Save                                     'otherwise EntryId is inaccessible
strEntryID = Item.EntryID
set Item = Nothing                           'dereference and reopen the item, otherwise Outlook overwrites MessageClass

set Item = Application.Session.GetItemFromID(strEntryID)
Item.MessageClass = "IPM.Note"

set rItem = CreateObject("Redemption.SafeMailItem")
rItem.Item = Item
rItem.Fields(PR_ICON_INDEX) = Empty          'delete the property, otherwise the message has a wrong icon
Item.Save

rItem.Import(...)                            'or call CopyTo(), or set the properties one by one
rItem.Save

set Item = Nothing
set rItem = Nothing

If you use the RDO family of objects, set RDOMail.Sent to true before the first save. Unlike Outlook Object Model, RDOMail.Sent is read/write before the first save.

This sample creates a new message in the sent state in the Inbox:

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

set Inbox = Session.GetDefaultFolder(olFolderInbox)
set Msg = Inbox.Items.Add("IPM.Note")
Msg.Sent = true                              'can only do this before the first Save()
Msg.Subject = "Test received message"
Msg.Body = "test body"
Msg.Unread = true

'sent/received dates
Msg.SentOn = Now
Msg.ReceivedTime = Now

'sender related props
msg.Sender = Session.CurrentUser
msg.SentOnBehalfOf = Session.CurrentUser

'all done, save
Msg.Save

Outlook XP becomes unstable after Redemption legacy

Various Redemption objects need an Extended MAPI session. After the session is retrieved, it is saved for later use by other Redemption objects. Outlook XP may not tolerate that cached session as well as other Outlook versions.

To make sure the session is not cached, create a Redemption.MAPIUtils object and call MAPIUtils.Cleanup after you are done with Redemption.

'this code goes after you are done with other Redemption objects
set Utils = CreateObject("Redemption.MAPIUtils")
Utils.Cleanup

Using Redemption with CDO legacy

Redemption can be used with CDO, not just with Outlook Object Model. Safe*Item objects only require the object assigned to Item to expose a MAPIOBJECT property, which most OOM and CDO objects do.

When using CDO, some Redemption calls are faster if you give Redemption the MAPI session already used by CDO:

set Session = CreateObject("MAPI.Session")
Session.Logon

'now you can tell Redemption that you want to use an existing session:
'all Redemption objects will be using that session from now on until
'Redemption.dll is unloaded.
set Utils = CreateObject("Redemption.MAPIUtils")
Utils.MAPIOBJECT = Session.MAPIOBJECT

HTML messages are delivered as plain text

Selecting an encoding programmatically has always been unclear in Outlook. Set the editor format and Internet mail override properties before sending.

set Appt = Application.CreateItem(olMailItem)
set sItem = CreateObject("Redemption.SafeMailItem")
sItem.Item = Appt
sItem.Recipients.Add "user@domain.com"
sItem.Recipients.ResolveAll
sItem.Subject = "test subject"
sItem.HTMLBody = "<html><body><b>bold</b> text</body></html>"

PR_InetMailOverrideFormat = &H59020003
ENCODING_PREFERENCE = &H00020000
BODY_ENCODING_TEXT_AND_HTML = &H00100000
ENCODING_MIME = &H00040000

PR_MSG_EDITOR_FORMAT = &H59090003
EDITOR_FORMAT_PLAINTEXT = 1
EDITOR_FORMAT_HTML = 2

sItem.Fields(PR_InetMailOverrideFormat) = ENCODING_PREFERENCE or ENCODING_MIME or BODY_ENCODING_TEXT_AND_HTML
sItem.Fields(PR_MSG_EDITOR_FORMAT) = EDITOR_FORMAT_HTML
sItem.Send

Outlook 98/2000 Internet Only Mode limitations legacy

There are no official workarounds. Microsoft did not support Extended MAPI in Internet Only Mode, which is one reason that mode was removed in Outlook 2002. Some features may work in some configurations, but there are no guarantees.

Objects, methods, and properties potentially affected in Internet Only Mode include:

AddressEntry object
MAPIUtils object
SafeMAPIFolder object
CurrentUser object
AddressLists object

Recipient.Resolve method
Recipient.FreeBusy method

Most Safe*Item object properties and methods should still work.

Using RFC822 import/export from C++ or Delphi

Redemption exports HrIMessageToRFC822Message() and HrRFC822MessageToIMessage() for code that works directly with Extended MAPI rather than Outlook Object Model or CDO. Load Redemption.dll with LoadLibrary(), then use GetProcAddress() to load these functions dynamically.

HrIMessageToRFC822Message

C/C++:
_stdcall HrIMessageToRFC822Message(LPMESSAGE pMsg, LPMAPISESSION pSession, ULONG *pCount, LPVOID FAR *lppBuffer)

Delphi:
function HrIMessageToRFC822Message(pMsg : IMessage; pSession : IMAPISession; var lpCount : ULONG; var lppBuffer : pointer): HResult; stdcall;

Parameters:

input : pMsg - IMessage, pSession - IMAPISession
output: lpCount - number of bytes in the output buffer, including trailing 0
        lppBuffer - output buffer with the RFC822 message, 0 terminated.
                    Must be freed with MAPIFreeBuffer.

HrRFC822MessageToIMessage

C/C++:
_stdcall HrRFC822MessageToIMessage(LPMESSAGE pMsg, LPMAPISESSION pSession, ULONG Count, LPVOID FAR lpBuffer)

Delphi:
function HrRFC822MessageToIMessage(pMsg : IMessage; pSession : IMAPISession; Count : ULONG; lpBuffer : pointer): HResult; stdcall;

Parameters:

input : pMsg - IMessage
        pSession - IMAPISession
        Count - number of bytes in the input buffer
        lpBuffer - input buffer with the RFC822 message

Example

typedef HRESULT (_stdcall *HrRFC822MessageToIMessage)(LPMESSAGE pMsg, LPMAPISESSION pSession, ULONG Count, LPVOID FAR lpBuffer);
HrRFC822MessageToIMessage pfnHrRFC822MessageToIMessage = NULL;

hInst = LoadLibrary("c:\\temp\\redemption.dll");
if (hInst) {
  pfnHrRFC822MessageToIMessage = (HrRFC822MessageToIMessage)GetProcAddress(hInst, "HrRFC822MessageToIMessage");

  // load an RFC822 formatted file into pszBuffer buffer of size ulFileSize
  ...

  // dump the contents of pszBuffer into an existing IMessage (pTargetMsg)
  if (S_OK == pfnHrRFC822MessageToIMessage(pTargetMsg, lpSession, ulFileSize, pszBuffer)) {
    pTargetMsg->SaveChanges(KEEP_OPEN_READWRITE);
  }
}

Selecting or changing the sending account

For account selection, use RDOMail.Account to read or set the account used for sending a message. You can also use Outlook 2007 and newer MailItem.SendUsingAccount.

You can do more than select an account: you can set the sender name and address to an arbitrary value. The trick is to add a named property with the name From. Outlook converts that property to an RFC822 From header when the message is converted to MIME.

Both Exchange and Internet Mail providers replace an existing From header, so recipients do not receive two headers. This works only when the message is converted to RFC822 along the way; it will not work between two mailboxes on the same Exchange server. The Sent Items copy still shows the default sender, but recipients see the new value.

set sItem = CreateObject("Redemption.SafeMailItem")
sItem.Item = MailItem

tag = sItem.GetIDsFromNames("{00020386-0000-0000-C000-000000000046}", "From")
tag = tag or &H1E    'the type is PT_STRING8
sItem.Fields(Tag) = "Someone <whoever@domain.com>"

sItem.Subject = sItem.Subject  'to trick Outlook into thinking that something has changed
sItem.Save

ReceivedTime and SentOn are reset during import

This is an Outlook behavior: it resets these two properties. As a workaround, use Redemption.MessageItem, which does not try to be smart.

MailItem.Save  'Save the OOM object just to make sure EntryID is available
EntryID = MailItem.EntryID  'remember the entry id

set Utils = CreateObject("Redemption.MAPIUtils")
set rMessage = Utils.GetItemFromID(EntryID)  'reopen the same message as Redemption.MessageItem

rMessage.Import "c:\test.eml", 1024
rMessage.Save

'important: do not modify and save the original object (MailItem) after this
'you will get an error saying that the message has changed

Errors after processing hundreds of Outlook items legacy

Create a Redemption object, such as Redemption.MAPIUtils, when the application starts and keep it referenced in a global variable until the application terminates. Each Redemption object calls MAPIInitialize when created and MAPIUninitialize when destroyed. Some Outlook versions had problems after hundreds of repeated initialize/uninitialize cycles.

This problem was fixed in Outlook 2003 SP1.

.NET code becomes unstable after many folder items

Your code most likely runs out of the 255 RPC channels per process limit enforced by Exchange. .NET does not immediately release COM objects, so it is easy to exceed the limit.

Call GC.Collect() periodically, release COM objects as soon as you are done with them using Marshal.ReleaseCOMObject(), and avoid multiple-dot notation such as Folder.Items.Item(index). Multiple-dot notation creates implicit variables that cannot be explicitly released.

Also look into using MAPITable, either as a standalone object or returned by RDOFolder.Items.MAPITable. It retrieves properties from multiple messages without opening each message, which avoids wasting RPC channels.