
本文详解如何通过 outlook javascript api 的 `onmessagesend` 事件,在用户点击“发送”按钮时拦截邮件、弹出分类面板并强制完成邮件安全分级,确保合规性要求落地。
在开发企业级 Outlook 邮件分类插件(如 Confidential/Internal/Public 分级)时,仅依赖 UI 按钮(如 ClassifyEmail)无法真正保障合规——用户可跳过分类直接发送。真正的强制校验必须绑定到发送生命周期本身。Microsoft 提供的 OnMessageSend 事件正是为此设计:它在 Outlook 客户端调用原生“发送”动作前触发,支持异步阻断、UI 弹窗与业务逻辑验证,是实现“发送前强管控”的唯一可靠方案。
✅ 正确实现路径:三步到位
1. 清单文件(manifest.xml)配置事件型权限
需启用 EventedTaskPane 权限,并声明 OnMessageSend 事件扩展点(注意:仅支持 Exchange Online / Outlook on Windows/macOS 最新版,不支持 Outlook on the web 的旧版渲染器):
<Permissions>ReadWriteItem</Permissions>
<Events>
<Event Type="OnMessageSend" FunctionExecution="synchronous" />
</Events>
<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">
<Hosts>
<Host xsi:type="MailHost">
<DesktopFormFactor>
<FunctionFile resid="functionFile" />
<ExtensionPoint xsi:type="Events">
<Event Type="OnMessageSend" FunctionExecution="synchronous" />
</ExtensionPoint>
</DesktopFormFactor>
</Host>
</Hosts>
<Resources>
<bt:Urls>
<bt:Url id="functionFile" DefaultValue="https://yourdomain.com/functions.html"/>
</bt:Urls>
</Resources>
</VersionOverrides>
</VersionOverrides>⚠️ 注意:FunctionExecution="synchronous" 表示该函数必须在发送前完成执行并明确返回 true(允许发送)或 false(阻止发送),否则 Outlook 将报错中断。
2. 编写事件处理函数(functions.html)
该页面不渲染 UI,仅承载 JS 逻辑。使用 Office.onReady 初始化后注册事件处理器:
使用 OAuth 2.0(设备代码流)访问 Microsoft Outlook 与 Microsoft Graph API,仅限读取 Mail.Read、Calendars.Read、Contacts.Read 权限。
<!-- functions.html -->
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
<script>
Office.onReady((info) => {
if (info.host === Office.HostType.Outlook) {
// 注册 OnMessageSend 事件处理器
Office.context.mailbox.addHandlerAsync(
Office.EventType.ItemSend,
onMessageSendHandler,
{ asyncContext: "send-validation" }
);
}
});
function onMessageSendHandler(event) {
const item = Office.context.mailbox.item;
// 检查是否已设置分类标识(例如主题前缀)
const subject = item.subject || "";
const hasClassification = /^\[(Confidential|Internal|Public)\]/.test(subject);
if (!hasClassification) {
// 阻止发送 + 弹出任务窗格(需提前注册 TaskPane)
Office.context.mailbox.displayAppointmentFormAsync(
Office.context.mailbox.item.itemId, // 仅用于上下文,非必需
{
taskPaneId: "classificationPane",
width: 400,
height: 500
}
);
// 显示友好提示(可选)
Office.context.mailbox.showNotificationMessageAsync(
"⚠️ 邮件未分类",
"请先打开右侧分类面板完成安全分级,再发送邮件。",
{ icon: "Error" }
);
// 关键:调用 event.completed() 并传入 false,阻断发送
event.completed({ allowEvent: false });
return;
}
// 分类有效,允许发送
event.completed({ allowEvent: true });
}
</script>3. 任务窗格(taskpane.html)提供交互式分类 UI
复用你原有 HTML 结构,但需增强健壮性(如防重复提交、状态反馈):
<!-- taskpane.html -->
<div class="container">
<h2>NMI 邮件安全分类</h2>
<div class="input-group">
<label for="classification">请选择安全级别:</label>
<select id="classification">
<option value="">— 请选择 —</option>
<option value="Confidential">机密(Confidential)</option>
<option value="Internal">内部(Internal)</option>
<option value="Public">公开(Public)</option>
</select>
</div>
<button id="applyBtn">应用分类并关闭</button>
</div>
<script>
document.getElementById("applyBtn").addEventListener("click", async () => {
const classification = document.getElementById("classification").value;
if (!classification) {
alert("请选择有效的分类级别");
return;
}
try {
const item = Office.context.mailbox.item;
// 更新主题(推荐方式:保留原主题,仅添加前缀)
const originalSubject = item.subject || "";
const newSubject = `[${classification}] ${originalSubject.replace(/^\[(Confidential|Internal|Public)\]\s*/, '')}`;
await new Promise((resolve, reject) => {
item.subject.setAsync(newSubject, (asyncResult) => {
if (asyncResult.status === Office.AsyncResultStatus.Succeeded) {
resolve();
} else {
reject(asyncResult.error);
}
});
});
// 可选:关闭任务窗格
Office.context.ui.closeContainer();
// 可选:通知用户成功
Office.context.mailbox.showNotificationMessageAsync(
"✅ 分类已应用",
`邮件已标记为【${classification}】级别`
);
} catch (err) {
console.error("分类设置失败:", err);
alert("分类应用失败,请重试。");
}
});
</script>? 关键注意事项总结
- 清单版本必须 ≥ 1.11:OnMessageSend 事件仅在 VersionOverridesV1_1 及以上支持;
- 仅限同步执行:event.completed({ allowEvent: true/false }) 必须在函数内调用,且不可异步延迟(不能 await 后再调);
-
任务窗格需预注册:确保 taskpane.html 在 manifest 中通过
或 正确声明; - 兼容性检查:使用 Office.context.requirements.isSetSupported('MailBox', '1.13') 判断运行环境是否支持;
- 用户体验优化:建议在任务窗格中缓存用户上次选择,避免重复操作;同时对已分类邮件禁用按钮或灰显提示。
通过以上结构化实现,你将构建出真正符合企业安全策略的 Outlook 分类插件——不是“建议分类”,而是“不分类就不能发”。这既是技术最佳实践,也是数据治理落地的关键一环。

















