Ich dachte, ich teile die Konfiguration, die ich für die Verwendung von AWS SES für ausgehende, Bounces und eingehende E-Mails erstellt habe. Es gibt definitiv einige Nuancen beim SES-Dienst, und es hat eine ganze Menge Ausprobieren gebraucht, um genau zu verstehen, wie er funktioniert. Dies ist eher ein Brain-Dump als eine Schritt-für-Schritt-Anleitung. Es sollte unnötig sein, aber auf eigene Gefahr verwenden. Und auf jeden Fall immer den Code, den andere geschrieben haben und den Sie implementieren, durchlesen und verstehen.
Hintergrund:
Ich arbeite daran, Discourse in AWS bereitzustellen und alle verfügbaren Dienste zu nutzen, um Zuverlässigkeit und Redundanz zu gewährleisten. Als Entwickler bin ich mit der Kommandozeile und dem Code vertrauter und wollte Automatisierung für Infrastructure as Code verwenden. Meine gesamte Umgebung wird mit Terraform bereitgestellt, aber ich habe versucht, die Webkonsole zu durchklicken und die Dinge so gut wie möglich abzustimmen. IAM und Richtliniendokumente sind hier nicht das Thema, aber ich glaube, ich habe darauf hingewiesen, wo Dinge benötigt werden.
Das Betreiben einer Postfix-Instanz scheint für eine einzelne Anwendung übertrieben zu sein. Die Verwendung eines POP3-Mailbox ist so sehr 90er Jahre. Also ging ich den AWS-Hasenbau hinunter.
Ich habe einige äußerst nützliche Beiträge gefunden, die mir bei meiner Suche geholfen haben:
- AWS SES / AWS Lambda mail receiver endpoint code?
- How to use Amazon SES for sending emails to users?
- Configure VERP to handle bouncing e-mails
Der Mail-Receiver-Container half mir auch zu verstehen, wie Discourse Nachrichten verarbeitet:
- Configure direct-delivery incoming email for self-hosted sites with Mail-Receiver
- Update mail-receiver to the release version
Anfangs erwartete ich, dass der AWS-Webhook-Endpunkt eingehende Nachrichten verarbeiten würde, aber nachdem ich den Code durchgegangen war, erkannte ich, dass dies nicht der Fall sein würde. Ich habe meinen Lambda-Receiver-Code auf dem ausgezeichneten Beispiel von @dltj basiert. Ich habe mich entschieden, SNS für die Nachrichtenlieferung anstelle von S3 zu verwenden.
Voraussetzungen
- AWS-Konto
- Kenntnisse in DNS und den E-Mail-bezogenen Aufzeichnungstypen
- Eine Domain (oder Subdomain), in der Sie Änderungen vornehmen können
Hinweise
- Alles Dokumentierte muss in derselben AWS-Region erstellt werden
- Fett und kursiv geschriebener Text wie dieser sind Ihre implementierungsspezifischen Werte
- Kursiv geschriebener Text sind Namen von Variablen, feste Werte oder UI-Elemente
Schritte
-
Erstellen Sie eine SES (Simple Email Service) Domain-Identität, ihre.domain, in einer der AWS-Regionen, die den E-Mail-Empfang unterstützen
-
Domain-Identität verifizieren
-
Erstellen Sie ein SNS (Simple Notification Service) Topic, feedback-sns-topic, für Feedback-Benachrichtigungen
-
Konfigurieren Sie die ihre.domain Domain-Identität
a. Aktivieren Sie die E-Mail-Feedback-Weiterleitung
b. Konfigurieren Sie Bounce- und Beschwerde- (nicht Zustellungs-) Feedback-Benachrichtigungen so, dass sie das SNS feedback-sns-topic Topic verwenden -
Erstellen Sie ein Abonnement für das SNS feedback-sns-topic Topic
a. Protokoll ist HTTPS (Sie verwenden doch nicht etwa noch HTTP?)
b. Setzen Sie den Endpunkt auf https://ihre.domain/webhooks/aws (siehe VERP-Post)
c. Wählen Sie “Rohnachrichtenlieferung aktivieren” -
Erstellen Sie ein weiteres SNS Topic, incoming-sns-topic, für eingehende E-Mails
-
Erstellen Sie einen SES E-Mail-Empfangsregel-Satz, inbound-mail-set, falls noch kein aktiver vorhanden ist. Wenn ja, verwenden Sie diesen, da nur ein aktiver Regel-Satz existieren kann
-
Erstellen Sie eine Empfangsregel im inbound-mail-set Regel-Satz
a. Setzen Sie die Empfängerbedingung auf ihre.domain
b. Fügen Sie die Aktion hinzu, um zum SNS Topic incoming-sns-topic zu veröffentlichen, Kodierung Base64 -
Erstellen Sie einen API-Schlüssel in Ihrer Discourse-Instanz für den Benutzer system, der die Aktion receive email für die Ressource email gewährt
-
Erstellen Sie ein Secret im Secret Manager, email-handler-secret, mit den folgenden Schlüsseln und ihren jeweiligen Werten:
- api_endpoint - https://ihre.domain/admin/email/handle_mail
- api_key - aus Schritt 9
- api_username - system, es sei denn, Sie haben in Schritt 9 etwas anderes verwendet
-
Erstellen Sie eine Lambda-Layer, lambda-receiver-layer, für die Laufzeit python3.10, die die Bibliotheken requests und aws-lambda-powertools enthält
-
Erstellen Sie eine Lambda-Funktion, email-receiver-lambda, für die Laufzeit python3.10 mit dem Receiver-Code:
# Copyright (c) 2023 Derek J. Lambert
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import json
import os
from typing import TypedDict
import requests
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities import parameters
from aws_lambda_powertools.utilities.data_classes import event_source
from aws_lambda_powertools.utilities.data_classes.sns_event import SNSEvent, SNSEventRecord
from aws_lambda_powertools.utilities.typing import LambdaContext
class Secret(TypedDict):
api_endpoint: str
api_username: str
api_key: str
service = os.getenv('AWS_LAMBDA_FUNCTION_NAME')
logger = Logger(log_uncaught_exceptions=True, service=service)
try:
SECRET_NAME = os.environ['SECRET_NAME']
except KeyError as e:
raise RuntimeError(f'Missing {e} environment variable')
AWS_EXTENSION_PORT = os.getenv('PARAMETERS_SECRETS_EXTENSION_HTTP_PORT', 2773)
EXTENSION_ENDPOINT = f'http://localhost:{AWS_EXTENSION_PORT}/secretsmanager/get?secretId={SECRET_NAME}'
def get_secret() -> Secret:
return parameters.get_secret(SECRET_NAME, transform='json')
def handle_record(record: SNSEventRecord):
sns = record.sns
sns_message = json.loads(sns.message)
try:
message_type = sns_message['notificationType']
message_mail = sns_message['mail']
message_content = sns_message['content']
message_receipt = sns_message['receipt']
except KeyError as exc:
raise RuntimeError(f'Key {exc} missing from message')
try:
receipt_action = message_receipt['action']
except KeyError as exc:
raise RuntimeError(f'Key {exc} missing from receipt')
try:
action_encoding = receipt_action['encoding']
except KeyError as exc:
raise RuntimeError(f'Key {exc} missing from action')
try:
mail_source = message_mail['source']
mail_destination = ','.join(message_mail['destination'])
except KeyError as exc:
raise RuntimeError(f'Key {exc} missing from mail')
logger.info(f'Processing SNS {message_type} {sns.get_type} record with MessageId {sns.message_id} from {mail_source} to {mail_destination}')
# 'email' is deprecated, but just in case something is configured incorrectly
body_key = 'email_encoded' if action_encoding == 'BASE64' else 'email'
request_body = {
body_key: message_content
}
secret = get_secret()
headers = {
'Api-Username': secret['api_username'],
'Api-Key': secret['api_key'],
}
response = requests.post(url=secret['api_endpoint'], headers=headers, json=request_body)
logger.info(response.text)
response.raise_for_status()
@event_source(data_class=SNSEvent)
@logger.inject_lambda_context
def lambda_handler(event: SNSEvent, context: LambdaContext):
for record in event.records:
handle_record(record)
-
Konfigurieren Sie die Lambda-Funktion email-receiver-lambda:
a. Fügen Sie den Layer lambda-receiver-layer hinzu
b. Fügen Sie den regionsspezifischen Layer für AWS Parameter Store hinzu
c. Fügen Sie die Umgebungsvariable SECRET_NAME mit dem Wert email-handler-secret hinzu
d. Wenn Sie zusätzliche Details protokollieren möchten, fügen Sie die Umgebungsvariable POWERTOOLS_LOGGER_LOG_EVENT mit dem Wert true hinzu -
Erteilen Sie der Lambda-Funktion email-receiver-lambda die IAM-Berechtigung secretsmanager:GetSecretValue für das Secret email-handler-secret
-
Erstellen Sie ein Abonnement für das SNS Topic incoming-sns-topic
a. Protokoll ist AWS Lambda
b. Setzen Sie den Endpunkt auf die ARN von email-receiver-lambda -
IAM-Berechtigungen werden für das SNS-Abonnement des incoming-sns-topic Topics benötigt, um email-receiver-lambda aufzurufen, aber ich glaube, dies geschieht automatisch, wenn es über die Konsole konfiguriert wird
Zu Debugging-Zwecken oder zur allgemeinen Selbstquälerei können Sie ein E-Mail-Abonnement für eines der SNS-Topics hinzufügen, um die Benachrichtigungen zu überwachen.
Ich habe das in ein paar Sitzungen niedergeschrieben, aber ich denke, es ist alles. Ich kann versuchen, allgemeine Fragen zu beantworten, wenn die Zeit es erlaubt.
