dkebler
(D Kebler)
2022 年1 月 10 日 15:41
1
我正在考虑实现邮件接收器的最佳方法(因为我已经在使用 AWS SES),那就是通过 Lambda 函数调用 API,然后调用 API 端点 /admin/email/handle_mail。
有一些关于如何将邮件发送到 SES,然后通过 S3 传递给 Lambda 函数的教程。还有一些现成的 Node.js 函数可以获取该邮件并将其转发到邮件服务器。
但与其这样做,不如直接从 Lambda 函数调用 API 并传递邮件。 (我没有维护自己的本地邮件服务器,也不想进行轮询)。
因此,我需要一些帮助,无论是提供更详细的文档(据我所知没有文档页面)关于如何调用该端点以及具体发送什么(这是一个 REST 端点吗?是否有 WebSocket 替代方案?发送内容的格式是什么?)还是提供一些代码示例。我还需要运行这个额外的容器吗?
Configure direct-delivery incoming email for self-hosted sites with Mail-Receiver (该帖子已经有 6 年了)
有人似乎用 Python 做到了这一点 Update mail-receiver to the release version - #7 by dltj ? 但没有详细信息,我需要用 Node.js/JavaScript 来完成。
有人有可用的 JavaScript 函数可以分享吗? 谢谢。
2 个赞
dltj
(Peter Murray)
2022 年1 月 10 日 19:01
2
@dltj 在此。我无法帮助处理 Node/JavaScript,但我可以提供一些伪代码,形式是我正在使用的 Python 代码:
""" AWS Lambda 接收来自 SES 的消息并将其发布到 Discourse"""
import json
import logging
import boto3
import requests
from base64 import b64decode
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.INFO)
# pylint: disable=C0301
ENCRYPTED_DISCOURSE_KEY = "{cyper-text}"
DISCOURSE_KEY = boto3.client("kms").decrypt(
CiphertextBlob=b64decode(ENCRYPTED_DISCOURSE_KEY)
)["Plaintext"]
DISCOURSE_SITE = "discuss.folio.org"
EMAIL_S3_BUCKET = "openlibraryfoundation-discourse-mail"
DISCOURSE_URL = f"https://{DISCOURSE_SITE}/admin/email/handle_mail"
DISCOURSE_API_HEADERS = {
"Api-Key": DISCOURSE_KEY,
"Api-Username": "system",
}
def log_lambda_context(context):
"""记录 Lambda 调用方提供的 context 对象的属性
"""
LOGGER.info(
"剩余时间 (毫秒): %s。 日志流: %s。 日志组: %s。 内存限制 (MB): %s",
context.get_remaining_time_in_millis(),
context.log_stream_name,
context.log_group_name,
context.memory_limit_in_mb,
)
def lambda_handler(event, context):
"""处理 SES 传递事件
"""
log_lambda_context(context)
LOGGER.info(json.dumps(event))
processed = False
for record in event["Records"]:
mail = record["ses"]["mail"]
mailMessageId = mail["commonHeaders"]["messageId"]
sesMessageId = mail["messageId"]
mailSender = mail["source"]
LOGGER.info(
"正在处理 SES 消息,mID %s (%s),发件人 %s",
mailMessageId,
sesMessageId,
mailSender,
)
deliveryRecipients = mail["destination"]
for recipientEmailAddress in deliveryRecipients:
LOGGER.info("传递收件人: %s", recipientEmailAddress)
s3resource = boto3.resource("s3")
bucket = s3resource.Bucket(EMAIL_S3_BUCKET)
obj = bucket.Object(sesMessageId)
LOGGER.info("正在获取 %s/%s", EMAIL_S3_BUCKET, sesMessageId)
post_data = {"email": obj.get()["Body"].read()}
LOGGER.info("正在发布到 %s", DISCOURSE_SITE)
r = requests.post(
DISCOURSE_URL, headers=DISCOURSE_API_HEADERS, data=post_data
)
if r.status_code == 200:
LOGGER.info("邮件已由 Discourse 接受: %s", DISCOURSE_SITE)
obj.delete()
processed = True
else:
LOGGER.error(
"邮件被 %s (%s) 拒绝: %s",
DISCOURSE_SITE,
r.status_code,
r.content,
)
return processed
我正在使用 Serverless 来管理 Lambda 的部署和维护。如果您想采用类似的方式,欢迎参考这个 serverless.yml 文件:
service: ses-post-to-discourse
frameworkVersion: "=1.36.1"
provider:
name: aws
runtime: python3.7
onError: arn:aws:sns:us-east-1:{AWS_ACCOUNT}:ses-discourse-DLQ
region: us-east-1
iamRoleStatements:
- Effect: 'Allow'
Action:
- 'ses:SendBounce'
Resource:
- '*'
- Effect: 'Allow'
Action:
- 's3:*'
Resource:
- 'arn:aws:s3:::openlibraryfoundation-discourse-mail/*'
- Effect: 'Allow'
Action:
- 'kms:decrypt'
Resource:
- 'arn:aws:kms:us-east-1:{AWS_ACCOUNT}:key/{UUID-of-key}'
package:
include:
# - something
exclude:
- node_modules/**
- .venv/**
- __pycache__
- secrets.yml
functions:
emailDispatcher:
handler: post_ses_delivery_lambda.lambda_handler
events:
- sns: ses-discourse-inbound
# CloudFormation 资源模板
resources:
Resources:
EmailDispatcherLogGroup:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: "30"
plugins:
- serverless-python-requirements
custom:
pythonRequirements:
pythonBin: .venv/bin/python
dockerizePip: false
dltj
(Peter Murray)
2022 年1 月 10 日 22:31
4
密钥不需要加密。我们项目的基础设施代码位于共享空间中,因此我不想将密钥硬编码到源代码中。
我的网站上启用了“手动轮询”,尽管我不记得它具体有什么作用。
除了发送和读/写临时存储桶之外,我记不起 AWS 权限有任何异常。
祝你好运!
dkebler
(D Kebler)
2022 年1 月 11 日 01:47
5
在 WebUI 中是否有生成 API 密钥的地方?在设置中找不到任何地方。也许您只需在构建时将其设置为您想要的任何内容作为环境变量?无论如何,我不知道如何生成 API 密钥。
dltj
(Peter Murray)
2022 年1 月 11 日 13:12
6
Discourse API 密钥?它在管理界面中生成,网址为 https://{discourse_url}/admin/api/keys
1 个赞
dkebler
(D Kebler)
2022 年1 月 11 日 17:41
7
是的,我看到了,但认为它不正确,因为“granular”没有 handle_email 权限,我不确定应该选择哪个用户或所有用户。
所以我为用户“system”生成了一个具有全局权限的密钥。你也是这么做的吗?
嗯,我当时想使用我的“postman”应用程序来学习/试用,但没有文档,我不知道要 POST 什么以及使用什么格式,以及如何传递密钥。如果我更熟悉 Python,也许可以通过查看你的代码来理解这一点。
我知道这是一个开源项目,所以可以理解为什么没有关于使用 handle_mail API 的实际文档,只能得到像你这样的好心人的帮助……谢谢!
pfaffman
(Jay Pfaffman)
2022 年1 月 11 日 18:56
8
它并非未被记录,因为它开源,而是因为没有人(或很少有人)询问过。
# frozen_string_literal: true
class Admin::EmailController < Admin::AdminController
def index
end
def server_settings
data = { delivery_method: delivery_method, settings: delivery_settings }
render_json_dump(data)
end
def test
params.require(:email_address)
begin
message = TestMailer.send_test(params[:email_address])
Email::Sender.new(message, :test_message).send
render json: { sent_test_email_message: I18n.t("admin.email.sent_test") }
rescue => e
render json: { errors: [e.message] }, status: :unprocessable_entity
This file has been truncated. show original
建议您将编码后的电子邮件 POST 到一个 email_encoded 东西。routes.rb 显示这是一个 POST 请求,路由是 https://discourse.example.com/admin/email/handle_mail。我从邮件接收器示例模板中获得了它(我建议您直接使用它,因为如果您那样做了,您早就完成了),但它也在 discourse/config/routes.rb at main · discourse/discourse · GitHub 中(除非您了解 rals,否则它并不清楚具体发生了什么)。
1 个赞
dkebler
(D Kebler)
2022 年1 月 12 日 05:11
9
“邮件接收者示例模板”
它在哪里?在仓库里吗? https://github.com/discourse/discourse?
我几乎从 Python 代码中弄明白了
dkebler
(D Kebler)
2022 年1 月 12 日 23:22
10
经过几天的努力,终于成功了!
@dltj 我已经使用 aws-sdk 和 got 模块将您的代码重写为 nodejs。非常感谢。
一路上有几个障碍,从 AWS(ses、IAM、S3、lambda)、搞定 serverless、正确进行 API 调用,到配置好 discourse 设置。
我将写一个详细的主题,介绍我为实现这一目标所做的具体工作,细节足够让其他人无需编码或抓狂就能启用此功能。此方法真正的优势在于,通过使用 ses,您无需 为域名设置电子邮件服务器。这样您就可以为您的域名设置一个完全独立的邮件服务器。
如果您对实现方法感兴趣,请稍后回来查看,准备好后我会在此处发布链接(几天后)。
3 个赞
martin
(Martin Brennan)
2022 年1 月 12 日 23:38
11
在此基础上,@dltj 我们将很快从 admin/email/handle_mail 路由中移除 email 参数,您需要将电子邮件正文作为 base64 编码字符串发送到此路由的 email_encoded 参数中。
3 个赞
dkebler
(D Kebler)
2022 年1 月 13 日 00:42
12
编码是我无法解决的问题之一
我无法让编码后的电子邮件正常工作
@martin 根据您的帖子,email_encoded 不正确。我两者都试过了,email_encoded 抛出错误,但 encoded_email 仍然给出警告。
body: 'warning: the email parameter is deprecated. all POST requests to this route should be sent with a base64 strict encoded encoded_email parameter instead. email has been received and is queued for processing',
我需要将收到的邮件转换为纯文本(我现在正在这样做)然后转回 base64,还是 AWS Ses 存储在存储桶中的内容已经是 base64?如果我将电子邮件正文按 SES 保存的方式传递,它就不会显示在收到的电子邮件日志中。POST 命令没有返回错误,只是警告。所以基本上我被卡在这里了。
请推迟删除 email 键,直到我们能够解决这个问题。
dkebler
(D Kebler)
2022 年1 月 13 日 01:09
13
即使我解码为纯文本并重新编码为 base64,我仍然会收到警告,并且电子邮件未显示为已收到
const en_package = {
headers: DISCOURSE_API_HEADERS,
json: {encoded_email:Buffer.from(body.toString(),'base64') }
}
console.log(Buffer.from(body.toString(),'base64'))
const res = await rest.post(DISCOURSE_URL,en_package)
再次,如果我发送纯文本“emal”,则会收到并处理相同的消息。
你一直说“strict”,那有什么意义吗? Nodejs/JS 只有两种 base64 风格“base64”“base64url”。
为什么即使我编码出错,当我使用 encoded_email 时 API 仍然会发出警告。
1 个赞
dkebler
(D Kebler)
2022 年1 月 13 日 02:06
14
我尝试了 curl,它不喜欢 encoded_email,但可以接受 email_encoded……真烦人,所以警告是错误的!
所以,我再次尝试了我的代码,但使用了 email_encoded,这次我使用了 base64 编码模块而不是内置的,它现在可以工作了,呼!
2 个赞
dltj
(Peter Murray)
2022 年1 月 13 日 02:52
15
啊!我现在知道了 。感谢你的提醒,@martin — 我会把它加到我的待办事项列表里。
2 个赞
martin
(Martin Brennan)
2022 年1 月 13 日 04:59
16
哇,这完全是我的错(搞错了警告信息),我会做一个小的 PR 来修复它。
2 个赞
martin
(Martin Brennan)
2022 年1 月 13 日 23:02
18
2 个赞
太棒了,感谢分享。
我们在 Lambda 上使用了已弃用的 base64 前版本三年,一个月前它失效了。
我们无法使用 SMTP :25,因为我们托管在 cloudflared tunnel 后面,而且既然 Amazon SES 能够很好地阻止噪音,为什么还要费力处理 25 端口呢?
总之,我快要崩溃了。
代码在 Postman 和本地计算机上运行的 Python 中都可以正常工作。从 AWS Lambda 发送相同的代码时,似乎有些东西被破坏了。
Forbidden
UTF-8
好了,解决了我的问题——
即使查询被传递了,‘机器人对抗模式’与 Discourse API 也不兼容。可能是因为 base64 编码?
希望这能为某人节省 72 小时生命:
关闭这个:
2 个赞