dkebler
(D Kebler)
1월 10, 2022, 3:41오후
1
이미 AWS SES를 사용하고 있으므로, mail-receiver를 구현하는 가장 좋은 방법은 Lambda 함수를 통해 API를 호출하여 /admin/email/handle_mail 엔드포인트를 호출하는 것 같습니다.
SES로 메일을 받아 S3를 통해 Lambda 함수로 전달하는 방법에 대한 튜토리얼이 많이 있습니다. 또한 해당 메일을 가져와 메일 서버로 중계하는 사전 구축된 Node.js 함수들도 있습니다.
하지만 그렇게 하는 대신, Lambda 함수에서 메일을 전달하면서 API를 직접 호출하는 것이 더 나은 것 같습니다. (저는 자체 로컬 메일 서버를 유지 관리하지 않으며, 어차피 폴링(polling)을 사용하고 싶지도 않습니다.)
따라서 해당 엔드포인트를 호출하는 방법과 정확히 무엇을 보내야 하는지에 대한 더 자세한 문서(AFAIK 문서 페이지가 없음)나 공유할 수 있는 코드가 있으면 도움이 될 것입니다. (그것은 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/JavaScript로 이를 수행해야 합니다.
작동하는 JavaScript 함수를 공유해 주실 수 있는 분이 있을까요? 감사합니다.
dltj
(Peter Murray)
1월 10, 2022, 7:01오후
2
@dltj입니다 . Node/JavaScript는 도와드릴 수 없지만, 이 작업을 위해 제가 사용 중인 Python으로 유사 코드(pseudo-code)를 제공해 드릴 수는 있습니다:
""" AWS Lambda to receive message from SES and post it to 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):
"""Log the attributes of the context object received from the Lambda invoker
"""
LOGGER.info(
"Time remaining (ms): %s. Log stream: %s. Log group: %s. Mem. limits(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):
""" Handle SES delivery event """
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(
"Processing an SES with mID %s (%s) from %s",
mailMessageId,
sesMessageId,
mailSender,
)
deliveryRecipients = mail["destination"]
for recipientEmailAddress in deliveryRecipients:
LOGGER.info("Delivery recipient: %s", recipientEmailAddress)
s3resource = boto3.resource("s3")
bucket = s3resource.Bucket(EMAIL_S3_BUCKET)
obj = bucket.Object(sesMessageId)
LOGGER.info("Getting %s/%s", EMAIL_S3_BUCKET, sesMessageId)
post_data = {"email": obj.get()["Body"].read()}
LOGGER.info("Posting to %s", DISCOURSE_SITE)
r = requests.post(
DISCOURSE_URL, headers=DISCOURSE_API_HEADERS, data=post_data
)
if r.status_code == 200:
LOGGER.info("Mail accepted by Discourse: %s", DISCOURSE_SITE)
obj.delete()
processed = True
else:
LOGGER.error(
"Mail rejected by %s (%s): %s",
DISCOURSE_SITE,
r.status_code,
r.content,
)
return processed
Lambda의 배포와 관리를 위해 Serverless 를 사용하고 있습니다. 유사한 경로를 따르시려면 이 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 resource templates
resources:
Resources:
EmailDispatcherLogGroup:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: "30"
plugins:
- serverless-python-requirements
custom:
pythonRequirements:
pythonBin: .venv/bin/python
dockerizePip: false
dkebler
(D Kebler)
1월 10, 2022, 10:03오후
3
감사합니다. JS가 아니더라도 좋은 시작이네요.
(암호화된) API 키는 어떻게 생성하나요? 암호화가 필요한가요?
API를 사용해 이메일 답변을 푸시하기 위해 "수동 폴링 활성화"를 체크해야 하는지, 아니면 기본적으로 활성화되어 있는 건지 확인이 필요합니다.
AWS 권한이나 SES 규칙과 관련하여 특별히 주의해야 할 점이 있나요?
dltj
(Peter Murray)
1월 10, 2022, 10:31오후
4
키를 암호화할 필요는 없습니다. 프로젝트의 인프라 코드는 공유 공간에 있으므로, 소스 코드에 키를 하드코딩하는 것을 원치 않았습니다.
제 사이트에는 "수동 폴링 활성화"가 설정되어 있지만, 구체적으로 어떤 기능을 하는지는 기억이 나지 않습니다.
임시 버킷에 대한 전송 및 읽기/쓰기 권한 외에 AWS 권한 설정에서 특이한 점은 없는 것 같습니다.
성공을 빕니다!
dkebler
(D Kebler)
1월 11, 2022, 1:47오전
5
webui에서 API 키를 생성하는 곳이 있는지 궁금합니다. 설정에서 해당 항목을 찾을 수가 없네요. 빌드할 때 환경 변수로 원하는 값을 직접 설정하는 건가요? 어쨌든 API 키를 생성하는 방법을 전혀 모르겠습니다.
dltj
(Peter Murray)
1월 11, 2022, 1:12오후
6
Discourse API 키는 관리자 인터페이스의 https://{discourse_url}/admin/api/keys에서 생성됩니다.
dkebler
(D Kebler)
1월 11, 2022, 5:41오후
7
네, 그건 봤는데 'granular’에 handle_email 권한이 없어서 정확히 어떤 사용자나 모든 사용자를 선택해야 할지 확신이 서지 않아서 맞다고 생각하지 못했어요.
그래서 ‘system’ 사용자를 위한 전역 권한이 있는 키를 생성했습니다. 그렇게 하신 건가요?
사실 ‘postman’ 앱을 사용해서 학습하고 테스트해보고 싶었는데, 문서가 없어서 어떤 포맷으로 무엇을 POST해야 하는지, 그리고 키를 어떻게 전달해야 하는지 모르겠어요. 파이썬에 더 익숙했다면 여러분의 코드를 보고 이해할 수 있었을지도 모르겠지만요.
이것이 오픈소스 프로젝트라는 것을 알고 있으므로 handle_mail API 사용에 대한 실질적인 문서가 없고 여러분 같은 분들의 도움만 있다는 점은 이해합니다… 감사합니다!
pfaffman
(Jay Pfaffman)
1월 11, 2022, 6: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
인코딩된 이메일을 email_encoded라는 필드에 POST로 보내라는 제안입니다. routes.rb를 보면 해당 요청이 POST 방식이며 라우트가 https://discourse.example.com/admin/email/handle_mail임을 알 수 있습니다. 이 정보는 메일 수신 샘플 템플릿에서 가져온 것입니다(사실 이 템플릿을 그대로 사용하셨다면 이미 작업을 완료하셨을 텐데, 그 사용을 권합니다). 또한 discourse/config/routes.rb at main · discourse/discourse · GitHub 관련 내용이 있습니다(라일을 이해하지 못하면 여기서 정확히 무슨 일이 일어나는지 바로 파악하기는 어렵습니다).
dkebler
(D Kebler)
1월 12, 2022, 5:11오전
9
dkebler
(D Kebler)
1월 12, 2022, 11:22오후
10
몇 일간의 노력 끝에 성공했습니다!
@dltj aws-sdk를 사용하여 당신의 코드를 nodejs로 재작성하고 모듈을 성공적으로 적용했습니다. 정말 감사합니다.
과정에 AWS(Ses, IAM, S3, lambda), serverless 설정, API 호출 방식, discourse 설정 등 여러 장애물이 있었습니다.
이제 제가 어떻게 이 문제를 해결했는지에 대해 상세한 주제를 작성할 예정입니다. 코딩을 전혀 하지 않고도 이 기능을 활성화할 수 있을 만큼 상세하게 작성하겠습니다. 이 방식의 가장 큰 장점은 ses를 사용하면 도메인용 이메일 서버를 별도로 설정할 필요가 없다는 것입니다. 이를 통해 도메인용 완전히 독립적인 메일 서버를 자유롭게 설정할 수 있습니다.
구현 과정에 관심이 있으시다면, 며칠 후 준비가 되는 대로 링크를 여기에 게시하겠습니다. 확인해 주세요.
martin
(Martin Brennan)
1월 12, 2022, 11:38오후
11
이어서 말씀드리자면, @dltj 님, 곧 admin/email/handle_mail 경로에서 email 파라미터를 제거할 예정입니다. 해당 경로에는 이메일 본문을 base64 인코딩된 문자열로 email_encoded 파라미터에 담아 보내야 합니다.
dkebler
(D Kebler)
1월 13, 2022, 12: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)
1월 13, 2022, 1: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가 계속 경고를 주는 건가요?
dkebler
(D Kebler)
1월 13, 2022, 2:06오전
14
curl을 시도해 봤는데, encoded_email은 작동하지 않고 email_encoded는 잘 작동하더라고요… 아, 경고 메시지가 틀린 거였군요!
그래서 다시 제 코드를 email_encoded로 시도해 봤는데, 이번에는 내장 모듈 대신 base64 인코딩 모듈을 사용했고, 다행히 잘 작동했습니다. 후우!
dltj
(Peter Murray)
1월 13, 2022, 2:52오전
15
아! 이제 그 부분 을 알겠습니다 . 알려주셔서 감사합니다, @martin — 제 할 일 목록에 추가하겠습니다.
martin
(Martin Brennan)
1월 13, 2022, 4:59오전
16
와, 제 실수였네요 (경고를 잘못 설정한 거예요). 수정하기 위해 마이너 PR을 올리겠습니다.
martin
(Martin Brennan)
1월 13, 2022, 11:02오후
18
정말 좋네요, 공유해 주셔서 감사합니다.
저희는 Lambda에서 3년 동안 비추천(deprecated)된 pre- base64 버전을 사용해 왔는데, 한 달 전에 갑자기 작동이 멈췄습니다.
cloudflared 터널 뒤에서 호스팅하고 있어서 SMTP :25 포트를 사용할 수 없어요. Amazon이 SES를 통해 노이즈를 아주 잘 차단해 주고 있는데, 굳이 25 포트로 고생할 이유가 없잖아요?
어쨌든, 이제 제 정신이 한계에 달았어요.
코드는 Postman이나 로컬 컴퓨터에서 실행하는 Python에서는 잘 작동합니다. 동일한 코드를 AWS Lambda에서 실행하면 뭔가 깨지는 것 같습니다.
Forbidden
UTF-8
음, 제 문제는 해결되었어요 -
쿼리는 전달되고 있었지만, 'bot fight mode’가 Discourse API와 잘 맞지 않는 것 같습니다. base64 인코딩 때문일까요?
누군가의 72시간을 절약해 줄 수 있기를 바랍니다:
이것을 꺼주세요: