如何用VSTO Add-In在Outlook 365特定事件触发时调用REST API
Outlook 365 VSTO插件开发问题
需求概述
我正尝试开发一款Outlook 365插件,实现以下功能:
- 当邮件被标记为跟进(今日、明日、本周等)时:向服务器发起REST API调用
http://localhost:8080/email/flagged,请求体包含邮件发件人、主题、正文、标记名称(今日、明日等),若邮件有唯一ID则一并发送。 - 当邮件取消跟进标记时:向服务器发起REST API调用
http://localhost:8080/email/unflagged/{uniqueEmailId},若有可用的邮件唯一ID则使用该路径,否则请求体包含邮件发件人和主题。
技术选型与疑问
我计划使用VSTO,但从未使用过它或其他微软Office插件工具。我有一些初始问题:
- Outlook插件能否向外部Web API发起HTTP REST调用?
ThisAddIn对象是否暴露了与邮件标记/取消标记相关的事件?
当前代码框架(VB.NET)
Dim WithEvents currentExplorer As Outlook.Explorer = Nothing Private Sub ThisAddIn_Startup(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Startup 'register a handler for the events I am interested in (need help here) End Sub Private Sub MyEventHandler(someEventObject) 'Get basic data (need help here too), eg: Dim eventName = someEventObject.EventType Dim EmailUniqueId = someEventObject.Uid Dim EmailFrom = someEventObject.FromEmailAddress Dim Subject = someEventObject.Subject 'Send an HTTP REST call (I know how to do that) End Sub
补充说明:我是一名Web开发者,倾向于使用HTTP REST API调用的解决方案。若此方案不可行,我也接受其他获取Outlook事件详情的思路,比如生成包含事件/邮件基础信息的文件,由定时任务解析。该项目仅为自用,无需过于优雅。
解决方案
疑问解答
- 可以发起外部REST调用:VSTO插件基于.NET框架,直接用
HttpClient(推荐)或WebClient类就能发送HTTP请求,和普通Web开发的调用逻辑一致,自用场景下无需额外复杂配置。 ThisAddIn不直接提供标记事件,但可通过其他对象监听:标记属于邮件的属性变更,可通过Explorer的选中事件结合MailItem的PropertyChange事件监听,或直接监听收件箱Items集合的ItemChange事件。
代码实现示例
VB.NET版本
Imports System.Net.Http Imports Outlook = Microsoft.Office.Interop.Outlook Public Class ThisAddIn Private WithEvents currentExplorer As Outlook.Explorer Private WithEvents selectedMail As Outlook.MailItem Private httpClient As New HttpClient() Private Sub ThisAddIn_Startup(sender As Object, e As EventArgs) Handles Me.Startup currentExplorer = Application.ActiveExplorer() ' 初始化当前选中邮件的事件监听 If currentExplorer.Selection.Count > 0 Then SetSelectedMail(currentExplorer.Selection(1)) End If End Sub ' 监听选中邮件变更 Private Sub currentExplorer_SelectionChange() Handles currentExplorer.SelectionChange If currentExplorer.Selection.Count > 0 Then SetSelectedMail(currentExplorer.Selection(1)) Else selectedMail = Nothing End If End Sub ' 绑定/解绑选中邮件的属性变更事件 Private Sub SetSelectedMail(item As Object) If TypeOf item Is Outlook.MailItem Then If selectedMail IsNot Nothing Then RemoveHandler selectedMail.PropertyChange, AddressOf MailItem_PropertyChange End If selectedMail = DirectCast(item, Outlook.MailItem) AddHandler selectedMail.PropertyChange, AddressOf MailItem_PropertyChange End If End Sub ' 监听邮件属性变更,重点关注标记相关属性 Private Sub MailItem_PropertyChange(Name As String) If Name = "FlagStatus" Or Name = "FlagRequest" Then ProcessMailFlagChange(selectedMail) End If End Sub ' 处理标记变更并调用API Private Async Sub ProcessMailFlagChange(mail As Outlook.MailItem) Dim uniqueId As String = mail.EntryID Dim from As String = mail.SenderEmailAddress Dim subject As String = mail.Subject Dim body As String = mail.Body Dim flagName As String = mail.FlagRequest Dim flagStatus As Outlook.OlFlagStatus = mail.FlagStatus Try If flagStatus = Outlook.OlFlagStatus.olFlagMarked Then ' 标记跟进:POST请求 Dim payload = New With { .EmailUniqueId = uniqueId, .EmailFrom = from, .Subject = subject, .Body = body, .FlagName = flagName } Await httpClient.PostAsJsonAsync("http://localhost:8080/email/flagged", payload) ElseIf flagStatus = Outlook.OlFlagStatus.olFlagComplete Or flagStatus = Outlook.OlFlagStatus.olFlagClear Then ' 取消标记:优先用ID路径,否则传发件人+主题 If Not String.IsNullOrEmpty(uniqueId) Then Await httpClient.DeleteAsync($"http://localhost:8080/email/unflagged/{uniqueId}") Else Dim payload = New With { .EmailFrom = from, .Subject = subject } Await httpClient.PostAsJsonAsync("http://localhost:8080/email/unflagged", payload) End If End If Catch ex As Exception ' 自用场景简单记录错误到本地文件 System.IO.File.AppendAllText("C:\OutlookPluginLogs.txt", $"{DateTime.Now}: {ex.Message}{Environment.NewLine}") End Try End Sub Private Sub ThisAddIn_Shutdown(sender As Object, e As EventArgs) Handles Me.Shutdown httpClient.Dispose() End Sub End Class
C#版本参考
using System; using System.Net.Http; using System.Net.Http.Json; using Microsoft.Office.Interop.Outlook; namespace OutlookFlagPlugin { public partial class ThisAddIn { private Explorer currentExplorer; private MailItem selectedMail; private HttpClient httpClient = new HttpClient(); private void ThisAddIn_Startup(object sender, EventArgs e) { currentExplorer = Application.ActiveExplorer(); currentExplorer.SelectionChange += CurrentExplorer_SelectionChange; // 初始化当前选中邮件监听 if (currentExplorer.Selection.Count > 0) { SetSelectedMail(currentExplorer.Selection[1]); } } private void CurrentExplorer_SelectionChange() { if (currentExplorer.Selection.Count > 0) { SetSelectedMail(currentExplorer.Selection[1]); } else { selectedMail = null; } } private void SetSelectedMail(object item) { if (item is MailItem mail) { // 解绑之前的事件 if (selectedMail != null) { selectedMail.PropertyChange -= MailItem_PropertyChange; } selectedMail = mail; selectedMail.PropertyChange += MailItem_PropertyChange; } } private void MailItem_PropertyChange(string Name) { if (Name == "FlagStatus" || Name == "FlagRequest") { ProcessMailFlagChange(selectedMail); } } private async void ProcessMailFlagChange(MailItem mail) { string uniqueId = mail.EntryID; string from = mail.SenderEmailAddress; string subject = mail.Subject; string body = mail.Body; string flagName = mail.FlagRequest; OlFlagStatus flagStatus = mail.FlagStatus; try { if (flagStatus == OlFlagStatus.olFlagMarked) { var payload = new { EmailUniqueId = uniqueId, EmailFrom = from, Subject = subject, Body = body, FlagName = flagName }; await httpClient.PostAsJsonAsync("http://localhost:8080/email/flagged", payload); } else if (flagStatus == OlFlagStatus.olFlagComplete || flagStatus == OlFlagStatus.olFlagClear) { if (!string.IsNullOrEmpty(uniqueId)) { await httpClient.DeleteAsync($"http://localhost:8080/email/unflagged/{uniqueId}"); } else { var payload = new { EmailFrom = from, Subject = subject }; await httpClient.PostAsJsonAsync("http://localhost:8080/email/unflagged", payload); } } } catch (Exception ex) { System.IO.File.AppendAllText(@"C:\OutlookPluginLogs.txt", $"{DateTime.Now}: {ex.Message}{Environment.NewLine}"); } } private void ThisAddIn_Shutdown(object sender, EventArgs e) { httpClient.Dispose(); } } }
备选方案(非VSTO方式)
如果VSTO事件监听不符合预期,可采用定时扫描方案:
- 用Windows任务计划搭配控制台程序,定期通过Outlook API扫描收件箱;
- 记录已处理的邮件ID避免重复操作;
- 直接调用REST API,或把邮件数据写入本地文件再由其他服务解析。
该方式实现更简单,无需开发插件,适合自用场景。
内容的提问来源于stack exchange,提问作者Drew
相关产品推荐
相关产品推荐

