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. 佐证材料
- 控制台重复执行日志截图:

- on-send重复触发复现演示视频:on-send重复触发复现演示视频
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
相关产品推荐
相关产品推荐

