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

如何用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插件工具。我有一些初始问题:

  1. Outlook插件能否向外部Web API发起HTTP REST调用?
  2. 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事件详情的思路,比如生成包含事件/邮件基础信息的文件,由定时任务解析。该项目仅为自用,无需过于优雅。


解决方案

疑问解答

  1. 可以发起外部REST调用:VSTO插件基于.NET框架,直接用HttpClient(推荐)或WebClient类就能发送HTTP请求,和普通Web开发的调用逻辑一致,自用场景下无需额外复杂配置。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 06:24:17