You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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三个属性,缺一不可。

验证方法

发送完成后:

  1. 检查代理邮箱的已发送文件夹,确认邮件存在且发件人为代理地址。
  2. 收件人收到的邮件发件人应为代理地址,无“代表xx发送”的标识。

内容的提问来源于stack exchange,提问作者Kevin Day

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 08:05:55