使用Extended MAPI和Outlook以代理发件人地址发件的问题
使用MAPI以代理地址作为发件人发送邮件的正确实现方案
核心问题分析
在Exchange/O365环境下,直接设置PR_SENDER_EMAIL_ADDRESS或用IAddrBook::CreateOneOff生成的PR_SENT_REPRESENTING_ENTRYID会导致邮件以“代表xx发送”的形式发出。本质原因是Exchange需要验证代理邮箱的合法权限,临时OneOff地址无法通过验证,必须使用Exchange地址簿中代理邮箱对应的有效EntryID。
关键解决步骤
1. 从Exchange地址簿解析代理邮箱的EntryID和StoreID
必须通过IAddrBook::ResolveName解析代理邮箱地址,获取对应的IMailUser对象,进而提取其PR_ENTRYID和PR_STORE_ENTRYID,而非使用临时OneOff地址。
示例代码:解析代理邮箱获取EntryID和StoreID
#include <mapix.h> #include <mapitags.h> // 定义属性标签数组 static const SizedSPropTagArray(1, sTagEntryID) = {1, {PR_ENTRYID}}; static const SizedSPropTagArray(1, sTagStoreEntryID) = {1, {PR_STORE_ENTRYID}}; HRESULT GetProxyMailUserIDs(LPMAPISESSION pSession, LPCWSTR szProxyEmail, LPSPropValue* ppSentRepEntryID, LPSPropValue* ppProxyStoreID) { if (!pSession || !szProxyEmail || !ppSentRepEntryID || !ppProxyStoreID) return E_INVALIDARG; HRESULT hr = S_OK; LPMAPIADRBOOK pAddrBook = nullptr; LPADRLIST pAdrList = nullptr; LPMAILUSER pMailUser = nullptr; SPropValue propEntryID = {0}, propStoreID = {0}; // 打开地址簿 hr = pSession->OpenAddressBook(nullptr, nullptr, 0, &pAddrBook); if (FAILED(hr)) goto Cleanup; // 解析代理邮箱地址 ULONG resolveFlags = MAPI_UNICODE; hr = pAddrBook->ResolveName(nullptr, resolveFlags, const_cast<LPWSTR>(szProxyEmail), nullptr, &pAdrList); if (FAILED(hr) || !pAdrList || pAdrList->cEntries == 0) { hr = MAPI_E_NOT_FOUND; goto Cleanup; } // 打开对应的MailUser对象 hr = pAddrBook->OpenEntry( pAdrList->aEntries[0].ulEIDSize, (LPENTRYID)pAdrList->aEntries[0].lpEntryID, nullptr, 0, nullptr, (LPUNKNOWN*)&pMailUser ); if (FAILED(hr)) goto Cleanup; // 获取PR_ENTRYID(用于PR_SENT_REPRESENTING_ENTRYID) hr = pMailUser->GetProps((LPSPropTagArray)&sTagEntryID, MAPI_UNICODE, &propEntryID.cValues, &propEntryID); if (FAILED(hr)) goto Cleanup; // 获取PR_STORE_ENTRYID(用于打开代理邮箱存储) hr = pMailUser->GetProps((LPSPropTagArray)&sTagStoreEntryID, MAPI_UNICODE, &propStoreID.cValues, &propStoreID); if (FAILED(hr)) goto Cleanup; // 复制属性值到输出参数(需手动释放内存) *ppSentRepEntryID = (LPSPropValue)malloc(sizeof(SPropValue) + propEntryID.Value.bin.cb); if (!*ppSentRepEntryID) { hr = E_OUTOFMEMORY; goto Cleanup; } memcpy(*ppSentRepEntryID, &propEntryID, sizeof(SPropValue)); memcpy((*ppSentRepEntryID)->Value.bin.lpb, propEntryID.Value.bin.lpb, propEntryID.Value.bin.cb); *ppProxyStoreID = (LPSPropValue)malloc(sizeof(SPropValue) + propStoreID.Value.bin.cb); if (!*ppProxyStoreID) { hr = E_OUTOFMEMORY; goto Cleanup; } memcpy(*ppProxyStoreID, &propStoreID, sizeof(SPropValue)); memcpy((*ppProxyStoreID)->Value.bin.lpb, propStoreID.Value.bin.lpb, propStoreID.Value.bin.cb); Cleanup: if (pMailUser) pMailUser->Release(); if (pAdrList) MAPIFreeBuffer(pAdrList); if (pAddrBook) pAddrBook->Release(); if (propEntryID.Value.bin.lpb) MAPIFreeBuffer(propEntryID.Value.bin.lpb); if (propStoreID.Value.bin.lpb) MAPIFreeBuffer(propStoreID.Value.bin.lpb); return hr; }
2. 在代理邮箱存储中创建并配置邮件
必须打开代理邮箱的邮件存储,在其发件箱中创建邮件,并设置正确的发件人属性:
示例代码:创建并发送代理邮箱邮件
HRESULT SendMailFromProxyAddress(LPMAPISESSION pSession, LPCWSTR szProxyEmail, LPCWSTR szSubject, LPCWSTR szRecipient) { HRESULT hr = S_OK; LPSPropValue pSentRepEntryID = nullptr; LPSPropValue pProxyStoreID = nullptr; LPMDB pProxyStore = nullptr; LPFOLDER pProxyOutbox = nullptr; LPMESSAGE pMsg = nullptr; LPADRLIST pRecipList = nullptr; // 1. 获取代理邮箱的EntryID和StoreID hr = GetProxyMailUserIDs(pSession, szProxyEmail, &pSentRepEntryID, &pProxyStoreID); if (FAILED(hr)) goto Cleanup; // 2. 打开代理邮箱的邮件存储 hr = pSession->OpenMsgStore( nullptr, pProxyStoreID->Value.bin.cb, (LPENTRYID)pProxyStoreID->Value.bin.lpb, nullptr, 0, &pProxyStore ); if (FAILED(hr)) goto Cleanup; // 3. 获取代理邮箱的发件箱 SPropValue propOutboxID = {0}; static const SizedSPropTagArray(1, sTagOutboxID) = {1, {PR_IPM_OUTBOX_ENTRYID}}; hr = pProxyStore->GetProps((LPSPropTagArray)&sTagOutboxID, MAPI_UNICODE, &propOutboxID.cValues, &propOutboxID); if (FAILED(hr)) goto Cleanup; hr = pProxyStore->OpenEntry( propOutboxID.Value.bin.cb, (LPENTRYID)propOutboxID.Value.bin.lpb, nullptr, MAPI_DEFERRED_ERRORS, nullptr, (LPUNKNOWN*)&pProxyOutbox ); if (FAILED(hr)) goto Cleanup; // 4. 创建新邮件 hr = pProxyOutbox->CreateMessage(nullptr, 0, &pMsg); if (FAILED(hr)) goto Cleanup; // 5. 设置发件人相关属性 SPropValue sendProps[] = { { PR_SENT_REPRESENTING_ENTRYID, PT_BINARY, { .bin = { pSentRepEntryID->Value.bin.cb, pSentRepEntryID->Value.bin.lpb } } }, { PR_SENT_REPRESENTING_NAME, PT_UNICODE, { .lpszW = const_cast<LPWSTR>(L"代理邮箱显示名") } }, // 可从MailUser对象获取PR_DISPLAY_NAME { PR_SENT_REPRESENTING_ADDRTYPE, PT_UNICODE, { .lpszW = const_cast<LPWSTR>(L"EX") } }, { PR_SUBJECT, PT_UNICODE, { .lpszW = const_cast<LPWSTR>(szSubject) } }, }; hr = pMsg->SetProps(_countof(sendProps), sendProps, nullptr); if (FAILED(hr)) goto Cleanup; // 6. 添加收件人(示例) hr = CreateOneOffRecipient(szRecipient, &pRecipList); // 需实现CreateOneOffRecipient函数 if (FAILED(hr)) goto Cleanup; hr = pMsg->ModifyRecipients(MODRECIP_ADD, pRecipList); if (FAILED(hr)) goto Cleanup; // 7. 保存并发送邮件 hr = pMsg->SaveChanges(KEEP_OPEN_READWRITE); if (FAILED(hr)) goto Cleanup; hr = pMsg->SubmitMessage(0); if (FAILED(hr)) goto Cleanup; Cleanup: if (pRecipList) MAPIFreeBuffer(pRecipList); if (pMsg) pMsg->Release(); if (pProxyOutbox) pProxyOutbox->Release(); if (pProxyStore) pProxyStore->Release(); if (pSentRepEntryID) free(pSentRepEntryID); if (pProxyStoreID) free(pProxyStoreID); if (propOutboxID.Value.bin.lpb) MAPIFreeBuffer(propOutboxID.Value.bin.lpb); return hr; }
关键注意事项
- 必须使用Exchange地址簿的EntryID:OneOff地址无法通过Exchange的代理权限验证,会被判定为“代表发送”。
- 邮件存储必须是代理邮箱:确保在代理邮箱的发件箱创建邮件,否则发送后会进入主邮箱的已发送文件夹。
- 属性完整性:需同时设置
PR_SENT_REPRESENTING_ENTRYID、PR_SENT_REPRESENTING_NAME、PR_SENT_REPRESENTING_ADDRTYPE三个属性,缺一不可。
验证方法
发送完成后:
- 检查代理邮箱的已发送文件夹,确认邮件存在且发件人为代理地址。
- 收件人收到的邮件发件人应为代理地址,无“代表xx发送”的标识。
内容的提问来源于stack exchange,提问作者Kevin Day
相关产品推荐
相关产品推荐

