【飞书集成平台】Outlook 邮件实时飞书消息提醒

1,248 阅读6分钟

原始诉求

  1. 团队重度使用飞书,但是很多重要的消息提醒仍然依赖公司邮箱系统,能否做到把Outlook 邮件在制定好规则后实时转发到飞书消息?
  2. 需要具备实时性,如果实时性无法达成,可接受每天定时转发亦可;

解决思路

通过飞书捷径

  1. 找到了飞书捷径有提供过类似的解决方案 飞书社区・一周精选 | 4 个掌握了就立刻能提高效率的飞书黑科技!

  1. 但是目前飞书捷径已经去除了 Outlook 的连接器;

通过 Microsoft Graph API 提取邮件

思路是通过 Microsoft Graph API 提取并识别最新邮件,找到符合规则的邮件,然后通过飞书消息实现转发;

实现路径

image.png

手动实践过程

第一步:创建 Azure Active Directory 应用

注册成功后,在应用概述页面可以看到 client idtenant id

第二步:创建证书和密码;

第三步:添加阅读邮件的委托权限

这里特别注意添加的应用委托权限,而不是应用程序权限,是因为应用程序权限是对工作账户内所有账号都有权限的,可能会有安全隐私等问题。使用 oauth 获取的 access_token 可以仅对登陆的账号有效,应用注册时也无需单独配置权限;

第四步:添加重定向地址,方便提取 access token 和 refresh token

第五步:本地登录,提取 access token 和 refresh token

from authlib.integrations.flask_client import OAuth
from flask import Flask, url_for, request
import urllib
import json

tenant_id = 'xxxx'
client_id = 'xxxxx'
client_secret = 'xxxxx'

app = Flask(__name__)

app.secret_key = 'xxxxx'
app.config['SESSION_TYPE'] = 'filesystem'

oauth = OAuth(app)

oauth.register(
    name='azure',
    client_id=client_id,
    client_secret=client_secret,
    access_token_url=f'https://login.chinacloudapi.cn/{tenant_id}/oauth2/v2.0/token',
    authorize_url=f'https://login.chinacloudapi.cn/{tenant_id}/oauth2/v2.0/authorize',
    client_kwargs={
        'scope': 'offline_access https://microsoftgraph.chinacloudapi.cn/Channel.ReadBasic.All https://microsoftgraph.chinacloudapi.cn/Mail.Read'},
)


@app.route('/')
def login():
    redirect_uri = url_for('authorize', _external=True)

    return oauth.azure.authorize_redirect(redirect_uri)


@app.route('/login/authorized')
def authorize():
    token = oauth.azure.authorize_access_token()
    print(token)
    with open("token.json", "w", encoding="utf8") as f:
        f.write(json.dumps(token)) # token 保存到了 token.json 文件中

    return token

# https://learn.microsoft.com/en-us/graph/webhooks?tabs=http#notification-endpoint-validation
@app.route("/notify", methods=['POST'])
def onedrive():
    valtoken = request.args.get('validationToken')

    resp = app.response_class(response=urllib.parse.unquote(valtoken), status=200, mimetype='plain/text')
    return resp

app.run(host='localhost', port=5000, debug=True)
  1. 服务启动后,本地访问,会提示验证登录动作;
  2. 这里特别注意的是,国外的 Microsoft Graph API 访问的域名和国内是不一致的learn.microsoft.com/en-us/previ… github 上很多正确的示例代码,怎么尝试都是错误的。
  3. 这里的过程只会使用一次,提取一次 refresh token 以后,后续,通过 refresh token 反复刷新 token 即可;

第六步:飞书集成平台创建集成流,监听创建订阅后的消息事件

  1. 创建订阅的过程参考 learn.microsoft.com/zh-cn/graph…

  2. 创建订阅

    url = "https://microsoftgraph.chinacloudapi.cn/v1.0/subscriptions"
    headers={
        'Authorization': 'Bearer %s' % access_token,
        'Content-type': 'application/json'
    }

    data = {
       "changeType": "created",
       "notificationUrl": "https://open.feishu.cn/xxxx/xxxx",
       "resource": "me/mailFolders('Inbox')/messages",
       "expirationDateTime":"2023-01-18T18:23:45.9356913Z",
    }

    resp = requests.post(url, headers=headers, json=data)

    print(resp.status_code, resp.json())
  1. 这里的 notificationUrl 是飞书集成平台集成流中对应的监听地址,这里需要将 Microsoft 的请求事件中的 query 部分完整返回;learn.microsoft.com/en-us/graph… 且需要注意的是,要确保 notificationUrl 有效:
    • A status code of HTTP 200 OK.
    • A content type of text/plain.
    • A body that includes the URL decoded validation token. Simply reflect back the same string that was sent in the validationToken query parameter.

第七步:接收到 Microsoft Graph API 事件,请求提取完整的邮件信息;

提取邮件信息.png

根据 resource 字段值,拼接完整的请求路径,提取完整的邮件体;

url = 'https://microsoftgraph.chinacloudapi.cn/v1.0/{resource}'

response = requests.get(url, headers={
    'Authorization': 'Bearer %s' % access_token
})

print(response.json())

第八步:制定转发规则,将需要提醒的邮件转发到对应的群组或者个人;

飞书集成平台集成流完整闭环

  1. JOSN助手:先将发过来的 webhook 消息体反序列化;

  2. 同步回调:立即返回 http status code 200;

    这里主要的原因是,需要应处理收到的每个更改通知。 应用程序必须至少执行以下任务来处理更改通知:learn.microsoft.com/zh-cn/graph…

    • 你的进程应处理它收到的每个更改通知并发送 2xx 类代码。 如果Microsoft Graph在 3 秒内未收到 2xx 类代码,它将尝试在大约 4 小时的时间内多次发布更改通知;之后,更改通知将被删除,并且不会送达。 如果进程在 3 秒内一致未响应,则通知可能会受到限制。
    • 如果处理预计需要 3 秒以上,则应保留通知,在响应Microsoft Graph时返回 202 - Accepted 状态代码,然后处理通知。 如果通知未保留,则返回 5xx 类代码以指示错误,以便重试通知。
    • 如果处理预计花费不到 3 秒,则应处理通知,并在响应Microsoft Graph时返回 200 - OK 状态代码。 如果通知未正确处理,则返回 5xx 类代码以指示错误,以便重试通知。
    • 验证 clientState 属性。 它必须与最初使用订阅创建请求提交的值匹配。
  3. 数据源:提取已经存储的 refresh token;

  4. Http client 连接器:根据 refresh token 换取 access token;

  5. 动态脚本:根据得到的 access token 和webhook 消息体重的 resource 字段,拼接请求参数;

function handler(input) {
  var url_path = input.url_path;
  var token = input.token;

  var url = `https://microsoftgraph.chinacloudapi.cn/v1.0/${url_path}`;
  var access_token = `Bearer ${token}`;
  return {
    url: url,
    access_token: access_token,
  };
}
  1. Http client 连接器:根据参数,发起 Get 请求,得到完整邮件内容;

  2. 动态脚本:根据返回值,提取需要的邮件消息体重要字段内容,拼接消息卡片消息体,需要注意的是,其中关于邮件正文的内容,直接根据 js 提取的除去 html string 格式化的内容;

var html_content = response.body.content;
var content = html_content
    .replace(/<[^<>]+>/g, '')
    .replace(/&nbsp;/gi, '')
    .replace(/&gt;/gi, '')
    .replace(/(^\s*)|(\s*$)/g, '');
  1. 自定义 飞书机器人:根据消息体发出邮件转发的卡片消息;

总结回顾

  1. Microsoft 已经提供了非常完备的消息订阅流程,learn.microsoft.com/zh-cn/graph… ,只要我们想,所有该有的事件都可以实时提取;
  2. 创建事件订阅后,需要及时的延长订阅事件的有效期,这里也是 Microsoft 版本更新后可能会让开发者误踩的坑之一;
  3. 先用 Python requests 请求将整个链路走通后,在飞书集成平台完整的实现,主要原因是,飞书集成平台的使用体验依然很稳定,我们不需要单独部署服务来实现过程的集成,一方面可以降低我们服务运维的成本,另一方面也是为了方便后续将此类工作解耦,交付其他团队使用;
  4. 后续,准备探索将整体 Outlook 邮件转发过程打包成连接器,开源给其他飞书集成平台同学使用;

参考文档