让你的代理通过电子邮件协商会议时间 - DEV Community

Dev.to AI 2026-06-25T15:10:54.587023

让你的代理通过电子邮件协商会议时间 - DEV Community

大多数"AI日程助手"演示都是作弊的。它们展示一个已经知道时间的模型,调用一次"创建事件",然后鞠躬谢幕。那不是日程安排——那是有聊天界面的数据录入。真正的日程安排是一种协商:有人问"你周二有空吗?",你查一下,回复"周二满了,周三下午2点怎么样?",对方再讨价还价,最终两个人类(或一个代理与一个人)达成一个大家都不讨厌的时间段。这是一个多轮对话,最终以日历事件结束。

我之前写过关于给你的代理设置日历的文章——配置一个真实邮箱,托管事件,回复邀请。那篇文章止于单一的create调用。本文则关注该调用之前的所有内容:来回沟通中时间被决定的过程。创建事件是故事的最后一句话,而非整个情节。

载体是Nylas Agent Account。它是一个拥有自己电子邮件地址和日历的授权,因此可以成为协商中的实际参与者,而不是一个在人类背后偷看的机器人。我在开发Nylas CLI,所以下面的终端命令正是我在原型设计这类循环时实际使用的命令。

有一点我要提前说明,因为这是最常见的错误方向:Scheduler 不适用于 Agent Accounts。 没有可用性配置API,没有预定页面,没有/v3/scheduling/*。这在支持端点中有文档记录。所以如果你本能地想"直接指向Scheduler",停下——那扇门对这个提供商来说是锁着的。你确实拥有的是该授权自己的空闲/忙碌状态以及Events API,这足以让你自己构建整个协商过程。本文将展示如何实现。

协商循环的实际样貌

撇开AI不谈,会议协商是一个包含四个步骤的状态机:

  1. 入站消息提出时间(或要求代理提出一些时间)。它以电子邮件的形式出现在一个线程中。
  2. 代理检查自己的空闲/忙碌状态,在请求的时间窗口内查找空闲时段。
  3. 代理在线程内回复,提出一个提议——接受请求的时间之一,或提出自己的反建议。
  4. 对方再次回复。使用新的约束条件回到步骤2,直到有人说"好",然后创建事件。

这四个步骤中有三个直接映射到Nylas原语——入站邮件、空闲/忙碌、在线程内回复——第四个是事件创建。不是Nylas调用的部分是中间的决策:从电子邮件中解析"周三下午可以,但2点之前不行"并将其转化为具体的时间段。那是你的应用逻辑——一个LLM(大语言模型)调用或你自己拥有的解析器。我将在整个过程中诚实地说明这个边界,因为假装API为你做推理正是我抱怨的那种演示魔法。

好消息是数据平面永远不会改变。Agent Account 只是一个带有grant_id的授权,所以下面的每个调用都是标准的/v3/grants/{grant_id}/...端点。没有新的东西要学——如果你使用过Nylas电子邮件和日历API,你已经知道这些动词。

开始之前

你需要:

刚接触Agent Accounts?先阅读什么是Agent Accounts——本文的其余部分假设你有一个可用的账户。

所有API示例使用美国基础主机https://api.us.nylas.com和bearer令牌。如果你的应用位于欧盟区域,请替换为api.eu.nylas.com

步骤1:读取入站请求

当有人给代理发邮件——"我们下周能抽出30分钟吗?"——那是一个带有thread_idmessage.created webhook。整个协商都发生在那个线程上,所以thread_id是你后续每一步都要携带的关键。参见代理的电子邮件线程了解为什么线程(而非主题行)是稳定的标识符。

获取邮件正文,以便你的代码可以提取请求的时间窗口:

curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/messages?thread_id=<THREAD_ID>&limit=5" \
  --header "Authorization: Bearer $NYLAS_API_KEY"

从CLI操作:

nylas email threads show <THREAD_ID>

……并完整读取特定消息:

nylas email read <MESSAGE_ID>

步骤 2:检查智能体自身的忙碌/空闲状态

现在智能体会在请求的时间范围内查看自己的日历。这一步让智能体成为真正的参与者:它有既定任务,且不应提议已被占用的时段。

API 端点为 POST /v3/grants/{grant_id}/calendars/free-busy。传入时间窗口和要检查的邮箱地址——智能体自身的地址用于检查其日历:

curl --request POST \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/calendars/free-busy" \
  --header "Authorization: Bearer $NYLAS_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "start_time": 1750766400,
    "end_time": 1751025600,
    "emails": ["support@yourcompany.com"]
  }'

响应返回该地址在时间窗口内的忙碌时间段列表(Unix 时间戳 start_time/end_time 对)。空闲时段则是它们之间的空隙——计算这些空隙同样由你的代码完成。free/busy 告诉你哪些时间已被占用;你决定哪些可被视作可提议时段(仅限工作时间?30 分钟粒度?会议间缓冲时间?)。

CLI 通过 nylas calendar availability check 封装了相同调用:

# 检查智能体自身在时间窗口内的忙碌块
nylas calendar availability check \
  --emails support@yourcompany.com \
  --start "next monday 9am" \
  --end "next friday 6pm"

此外还有 nylas calendar availability find,它更进一步,直接呈现空闲会议时段——当你在原型开发阶段且不想编写空闲时段查找逻辑时非常方便。但在生产环境的协商循环中,你通常需要从 check(或 API)获取原始的忙碌块,以便自己的代码控制如何选择时段。

一个微妙但重要的点:此处的 free/busy 检查的是智能体的日历。如果你还想检查人类的空闲情况,且该人类位于你应用可访问的已授权 Google 或 Microsoft 账户上,你可以在同一个 emails 数组中包含其地址——但同一请求中的所有地址必须属于同一提供商。然而,对于纯邮件的协商,你通常无法编程访问对方日历。这正是你通过邮件协商而非直接查询对方 free/busy 的原因。善加利用:提议、让对方还价、达成一致。

步骤 3:通过线程内回复提议时间

智能体已获取空闲时段。现在它需要提议这些时段——而提议是现有线程中的邮件回复,而非日历邀请。这正是与“直接创建事件”方法的核心区别所在。你尚未预定任何事,而是抛出两三个选项并等待。

在线程内回复至关重要,因为它能将整个协商保留在同一对话中,位于对方收件箱里,这样他们会很自然地再次回复。当你传递 reply_to_message_id 时,Nylas 会为你保留线程头(In-Reply-ToReferences):

curl --request POST \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/messages/send" \
  --header "Authorization: Bearer $NYLAS_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "reply_to_message_id": "<INBOUND_MESSAGE_ID>",
    "to": [{ "email": "alex@acme.com" }],
    "subject": "Re: 30 minutes next week",
    "body": "周二我已排满,但周三东部时间下午 2:00 或 3:30 有空,或者周四东部时间上午 11:00 有空。这些时间您方便吗?"
  }'

CLI 版本更简洁,因为 reply 会自动获取原始邮件并填充收件人、主题和线程信息:

nylas email reply <INBOUND_MESSAGE_ID> \
  --body "周二我已排满,但周三东部时间下午 2:00 或 3:30 有空,或者周四东部时间上午 11:00 有空。这些时间您方便吗?"

邮件正文中的措辞——如何表达选项、选择提供多少个选项、匹配请求者的偏好——由你的 LLM 根据你在步骤 2 中计算出的空闲时段生成。发送和线程处理由 Nylas 完成;措辞和选择哪些时段则由你决定。

步骤 4:读取对方还价并达成一致

这一步大多数演示完全跳过,但正是它让整个过程成为真正的协商。对方回复:“周三下午 2 点不行,你能改成周三早上吗?” 该回复会再次触发同一线程上的 message.created。你的处理程序在状态存储中查找 thread_id,发现该对话正处于协商过程中,然后带着新约束(“周三早上”)循环回到步骤 2。

有两个因素能让这个循环在生产环境中得以存活:

每一轮仅仅是重复步骤 2 和 3——在缩小后的时间窗口内重新检查空闲/忙碌状态,并在原邮件线程中回复一个更紧凑的提案。当一方的回复是接受时,你们就达成了共识;你的解析器检测接受的方式与检测原始请求相同:通过阅读邮件。

步骤 5:预订商定的时段

一旦双方都同意某个具体时间,现在创建事件——这是其他演示中首先展示的那个单一调用。由于 agent 是真正的日历参与者,创建时使用 notify_participants=true 会从 agent 的地址发送正常的 ICS 邀请。请求方会在 Gmail 或 Outlook 中收到它,像对待任何同事一样点击接受。

calendar_id 是创建时的必需查询参数;对于 agent 的默认日历,使用 primary

curl --request POST \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/events?calendar_id=primary&notify_participants=true" \
  --header "Authorization: Bearer $NYLAS_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "title": "Acme <> Support sync",
    "when": { "start_time": 1750944600, "end_time": 1750946400 },
    "participants": [
      { "email": "alex@acme.com" }
    ]
  }'

CLI 对应命令如下:

nylas calendar events create \
  --calendar primary \
  --title "Acme <> Support sync" \
  --start "2026-06-26 14:00" \
  --end "2026-06-26 14:30" \
  --participant alex@acme.com

执行此命令后,一个 event.created webhook 会到达 agent 的日历,当请求方点击接受时,他们的响应会回流到 agent 的...

查看原文