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

Outlook on-send add-in 发信时重复触发导致逻辑异常问题

Outlook On-Send 插件允许发送时事件二次触发问题排查

问题现象

  • 参照微软官方Outlook on-send add-in开发文档实现邮件发送校验插件,核心功能为校验用户撰写的邮件内容,命中预设拦截规则时阻止邮件发送
  • 异常表现:校验未命中拦截条件、判定邮件允许正常发送时,on-send事件会二次触发,导致原有业务逻辑执行混乱

相关材料

1. 插件Manifest配置

<?xml version="1.0" encoding="utf-8"?>
<OfficeApp
  xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0"
  xmlns:mailappor="http://schemas.microsoft.com/office/mailappversionoverrides"
  xsi:type="MailApp">

  <Id>baf2dcf2-21c6-41ac-9c06-1ea235844d9b</Id>
  <Version>3.15.2.7</Version>
  <ProviderName>TeamsAssist</ProviderName>
  <DefaultLocale>en-us</DefaultLocale>
  <DisplayName DefaultValue="TeamsAssist Notifications" />
  <Description DefaultValue="TeamsAssist On-Send Notifications" />
  <IconUrl DefaultValue="~remoteAppUrl/Images/TeamsAssistLogo64x64.png" />
  <HighResolutionIconUrl DefaultValue="~remoteAppUrl/Images/TeamsAssistLogo128x128.png" />
  <SupportUrl DefaultValue="https://cloudassist.co/" />
  
  <AppDomains>
    <AppDomain>https://teamsassistoutlookserver.azurewebsites.net</AppDomain>
  </AppDomains>

  <Requirements>
    <Sets DefaultMinVersion="1.1">
      <Set Name="Mailbox" />
    </Sets>
  </Requirements>

  <FormSettings>
    <Form xsi:type="ItemRead">
      <DesktopSettings>
        <SourceLocation DefaultValue="~remoteAppUrl/index.html?serverURL=https://teamsassistoutlookserver.azurewebsites.net/" />
        <RequestedHeight>250</RequestedHeight>
      </DesktopSettings>
    </Form>
  </FormSettings>

  <Permissions>ReadWriteItem</Permissions>

  <Rule xsi:type="RuleCollection" Mode="Or">
    <Rule xsi:type="ItemIs" ItemType="Message" FormType="Read" />
  </Rule>

  <VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides" xsi:type="VersionOverridesV1_0">
    <VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1" xsi:type="VersionOverridesV1_1">
      <Description resid="residAppDescription" />
      <Requirements>
        <bt:Sets DefaultMinVersion="1.3">
          <bt:Set Name="Mailbox" />
        </bt:Sets>
      </Requirements>
      <Hosts>
        <Host xsi:type="MailHost">
          <DesktopFormFactor>
            <FunctionFile resid="residUILessFunctionFileUrl" />
            <ExtensionPoint xsi:type="Events">
              <Event Type="ItemSend" FunctionExecution="synchronous" FunctionName="validateEmail" />
            </ExtensionPoint>
          </DesktopFormFactor>
        </Host>
      </Hosts>
      <Resources>
        <bt:Urls>
          <bt:Url id="residUILessFunctionFileUrl" DefaultValue="~remoteAppUrl/index.html?serverURL=https://teamsassistoutlookserver.azurewebsites.net/" ></bt:Url>
        </bt:Urls>
      </Resources>
    </VersionOverrides>
  </VersionOverrides>
</OfficeApp>

2. 最小复现核心代码

'use strict'

console.log('\nVersion 3.4')

// entry point function, called 'On Send'; see Manifest(s)
function validateEmail(event) {
    Office.onReady(function (info) {
        let mailboxItem = Office.context.mailbox.item

        mailboxItem.body.getAsync('html', { asyncContext: event }, function (asyncResult) {
            var allowSend = true
            asyncResult.asyncContext.completed({ allowEvent: allowSend })
        })
    })

}

3. 佐证材料

4. 完整业务逻辑代码

// TeamsAssist Outlook OnSend App
// Based ***initially*** on https://github.com/OfficeDev/Outlook-Add-in-On-Send
//
//    Date     Who Version  Comment
// ----------- --- -------  -----------------------------------------------------------------------------------------------------------------------------------

// ----------------------------------------------------------------------------------------------------------------------
'use strict'

const version = '3.13.1' // see also version in Manifest

// set both to false for Production
const testing = {
    EnableConsoleLog    : true,     // true for detailed console log
    NeverSend           : false     // true to prevent sending, even if no Notification or Block, for testing purposes only
}

if (testing.EnableConsoleLog) console.log('\nVersion (OnSend): ' + version)

// entry point function, called 'On Send'; see Manifest(s)
async function validateEmail(event) {
    await getEmailProperties()

    user.MailboxItem.body.getAsync('html', { asyncContext: event }, function (asyncResult) {
        // Trim user FirstName, if necessary
        const userFirstName = user.FirstName.length > global.MaxUserFirstNameLength
            ? user.FirstName.substring(0, global.MaxUserFirstNameLength) + ' ...'
            : user.FirstName

        // Define Notification Message
        email.Notification.Message = 'Hi ' + userFirstName + '; a Notification has occurred; this Email is Blocked. Go to TeamsAssist Help to assist you'

        // Trim Message anyway, if necessary
        if (email.Notification.Message.length > global.MaxNotificationMessageLength)
            email.Notification.Message = email.Notification.Message.substring(0, global.MaxNotificationMessageLength)

        OutlookLog('OnSend', email)

        if (email.Notification.Type === 'NoNotification' || !email.IsBlocked) {
            if (testing.EnableConsoleLog) console.log('Send Allowed')
            user.MailboxItem.notificationMessages.removeAsync(global.NotificationMessageID);
            asyncResult.asyncContext.completed({ allowEvent: true })
        } else {
            if (testing.EnableConsoleLog) console.log('Send Blocked')
            user.MailboxItem.notificationMessages.replaceAsync(global.NotificationMessageID, { type: 'errorMessage', message: email.Notification.Message })
            asyncResult.asyncContext.completed({ allowEvent: false })
        }
    })
}

问题根因与修复方案

  • 根因1:事件处理函数内部重复调用Office.onReady。On-send事件触发时Outlook会重新加载函数文件,若在事件入口内注册Office.onReady回调,同一次事件生命周期内回调可能被重复执行,带动后续校验逻辑多次运行。修复方式:将Office.onReady初始化逻辑移到事件函数外层,全局仅初始化一次,事件触发时直接判断初始化状态即可。
  • 根因2:异步操作未完成就调用event.completed放行。允许发送分支中调用了notificationMessages.removeAsync修改邮件项,但未等待该异步操作执行完成就直接返回允许发送的结果。Outlook客户端检测到发送流程中邮件项存在未完成的异步变更时,会自动重新触发一次ItemSend事件做二次校验,直接导致事件重复触发。修复方式:所有针对邮件项的异步操作(增删通知、修改正文/属性等)全部执行完成后,再调用event.completed返回校验结果。

修复后最小可运行代码示例

'use strict'
let isOfficeInitialized = false;
console.log('\nVersion 3.4 Fixed')

// 初始化逻辑移到外层,全局仅执行一次
Office.onReady(function (info) {
    isOfficeInitialized = true;
})

// 事件入口函数
function validateEmail(event) {
    if (!isOfficeInitialized) {
        event.completed({ allowEvent: false, errorMessage: "插件初始化中,请稍后重试" });
        return;
    }
    let mailboxItem = Office.context.mailbox.item
    mailboxItem.body.getAsync('html', { asyncContext: event }, function (asyncResult) {
        var allowSend = true
        if (allowSend) {
            // 等待所有邮件项修改操作完成后再放行
            mailboxItem.notificationMessages.removeAsync(global.NotificationMessageID, function() {
                asyncResult.asyncContext.completed({ allowEvent: true })
            })
        } else {
            mailboxItem.notificationMessages.replaceAsync(global.NotificationMessageID, { type: 'errorMessage', message: "邮件不符合发送规则" }, function() {
                asyncResult.asyncContext.completed({ allowEvent: false })
            })
        }
    })
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 04:21:42