AWS SES / AWS Lambda Mail-Empfänger-Endpunkt-Code?

Ich denke, der beste Weg, einen Mail-Empfänger zu implementieren (da ich bereits AWS SES verwende), ist die Verwendung der API über eine Lambda-Funktion, um den API-Endpunkt /admin/email/handle_mail aufzurufen.

Es gibt Tutorials, wie man E-Mails an SES sendet und diese über S3 an eine Lambda-Funktion weiterleitet. Es gibt auch vorgefertigte Node.js-Funktionen, um diese E-Mail abzurufen und an einen Mailserver weiterzuleiten.

Aber anstatt das zu tun, scheint es besser zu sein, die API direkt von einer Lambda-Funktion aufzurufen und die E-Mail zu übergeben. (Ich betreibe keinen eigenen lokalen Mailserver und möchte sowieso kein Polling verwenden).

Daher könnte ich etwas Hilfe gebrauchen, entweder durch detailliertere Dokumentation (soweit ich weiß, gibt es keine Dokumentationsseiten) zum Aufrufen dieses Endpunkts und was genau gesendet werden muss (ist das ein REST-Endpunkt? Gibt es eine WebSocket-Alternative? Format dessen, was gesendet werden soll?) oder durch Code zum Teilen. Muss ich diesen zusätzlichen Container immer noch ausführen?
Configure direct-delivery incoming email for self-hosted sites with Mail-Receiver (dieser Beitrag ist jetzt 6 Jahre alt)

Jemand hat das anscheinend mit Python gemacht Update mail-receiver to the release version - #7 by dltj? aber es gibt keine Details und ich muss das in Node/JavaScript tun.

Hat jemand eine funktionierende JavaScript-Funktion, die er teilen kann? Danke

2 „Gefällt mir“

@dltj hier. Ich kann nicht mit Node/JavaScript helfen, aber ich kann etwas Pseudocode in Form von Python anbieten, das ich verwende, um dies zu ermöglichen:

""" AWS Lambda, um Nachrichten von SES zu empfangen und an Discourse zu posten """
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):
    """Protokolliert die Attribute des vom Lambda-Invoker empfangenen Kontextobjekts"""
    LOGGER.info(
        "Verbleibende Zeit (ms): %s. Log-Stream: %s. Log-Gruppe: %s. Speicherlimits (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):
    """Behandelt das SES-Zustellungsereignis"""
    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(
            "Verarbeite eine SES mit mID %s (%s) von %s",
            mailMessageId,
            sesMessageId,
            mailSender,
        )

        deliveryRecipients = mail["destination"]
        for recipientEmailAddress in deliveryRecipients:
            LOGGER.info("Zustellungs-Empfänger: %s", recipientEmailAddress)

            s3resource = boto3.resource("s3")
            bucket = s3resource.Bucket(EMAIL_S3_BUCKET)
            obj = bucket.Object(sesMessageId)
            LOGGER.info("Hole %s/%s", EMAIL_S3_BUCKET, sesMessageId)
            post_data = {"email": obj.get()["Body"].read()}

            LOGGER.info("Poste an %s", DISCOURSE_SITE)
            r = requests.post(
                DISCOURSE_URL, headers=DISCOURSE_API_HEADERS, data=post_data
            )
            if r.status_code == 200:
                LOGGER.info("Mail von Discourse akzeptiert: %s", DISCOURSE_SITE)
                obj.delete()
                processed = True
            else:
                LOGGER.error(
                    "Mail von %s (%s) zurückgewiesen: %s",
                    DISCOURSE_SITE,
                    r.status_code,
                    r.content,
                )

    return processed

Ich verwende Serverless, um die Bereitstellung und Wartung des Lambda zu verwalten. Falls Sie einen ähnlichen Weg einschlagen möchten, können Sie gerne mit dieser serverless.yml-Datei beginnen:

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-Ressourcentemplates
resources:
  Resources:
    EmailDispatcherLogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        RetentionInDays: "30"

plugins:
  - serverless-python-requirements
custom:
  pythonRequirements:
    pythonBin: .venv/bin/python
    dockerizePip: false

Danke, guter Anfang, auch wenn es nicht JS ist.

  1. Wie erstellt man diesen (verschlüsselten) API-Schlüssel? Muss er verschlüsselt werden?
  2. Muss ich „Manuelles Abrufen aktiviert“ Push-E-Mails über die API für E-Mail-Antworten aktivieren, damit der API-Endpunkt aktiv ist, oder ist er standardmäßig aktiv?
  3. Gibt es etwas, auf das man in Bezug auf AWS-Berechtigungen oder SES-Regeln besonders achten sollte?

Der Schlüssel muss nicht verschlüsselt werden. Der Infrastrukturcode für unser Projekt befindet sich in einem gemeinsamen Bereich, daher wollte ich den Schlüssel nicht fest in die Quelle codieren.

Ich habe zwar „manuelles Abrufen aktiviert“ auf meiner Website, erinnere mich aber nicht mehr genau, was das bewirkt.

Nichts Ungewöhnliches, soweit ich mich erinnern kann, mit den AWS-Berechtigungen über das Senden und Lesen/Schreiben in den temporären Bucket hinaus.

Viel Erfolg!

Gibt es im WebUI eine Stelle, an der man den API-Schlüssel generiert? Ich kann nirgends in den Einstellungen etwas finden. Vielleicht setzt man ihn einfach als Umgebungsvariable, wenn man ihn erstellt? Jedenfalls habe ich keine Ahnung, wie man einen API-Schlüssel generiert.

Der Discourse API-Schlüssel? Er wird in der Admin-Oberfläche unter https://{discourse_url}/admin/api/keys generiert.

1 „Gefällt mir“

Ja, das habe ich gesehen, aber ich dachte nicht, dass es richtig ist, weil „granular“ keine handle_email-Berechtigung hatte und ich unsicher war, welchen Benutzer oder alle Benutzer ich wählen sollte.

Ich habe also einen Schlüssel für den Benutzer „system“ mit globaler Berechtigung generiert. Haben Sie das auch getan?

Nun, ich wollte meine „Postman“-App verwenden, um sie auszuprobieren, aber ohne Dokumentation weiß ich nicht, was ich in welchem Format POSTen soll und wie ich den Schlüssel übergeben soll. Wenn ich mit Python vertrauter wäre, könnte ich das vielleicht durch einen Blick auf Ihren Code verstehen.

Ich weiß, dass dies ein Open-Source-Projekt ist, daher ist es verständlich, dass es keine wirkliche Dokumentation zur Verwendung der handle_mail-API gibt, nur etwas Hilfe von Leuten wie Ihnen … danke!

Es ist nicht undokumentiert, weil es Open Source ist, es ist undokumentiert, weil niemand (oder nicht viele) danach gefragt haben.

Vorschläge, dass Sie die kodierte E-Mail in einem email_encoded-Dingens POSTEN möchten. routes.rb zeigt, dass es sich um einen POST und die Route handelt, die https://discourse.example.com/admin/email/handle_mail lautet. Ich habe es aus der E-Mail-Empfänger-Beispielvorlage (und ich würde empfehlen, dass Sie diese einfach verwenden, da Sie, wenn Sie es getan hätten, bereits fertig wären), aber es ist auch in discourse/config/routes.rb at main · discourse/discourse · GitHub enthalten (was nicht sofort klar ist, was passiert, es sei denn, Sie verstehen rals).

1 „Gefällt mir“

“mail receiver sample template”
Wo befindet es sich? Im Repository? GitHub - discourse/discourse: A platform for community discussion. Free, open, simple.?

Ich konnte es dank des Python-Codes ziemlich gut herausfinden.

Erfolg nach tagelanger Anstrengung!

@dltj Ich konnte Ihren Code mit dem aws-sdk und got-Modulen in Node.js umschreiben. Vielen Dank.

Es gab einige Hindernisse, von AWS (ses, IAM, S3, Lambda), dem Umgang mit Serverless, der korrekten API-Anfrage bis hin zur richtigen Konfiguration von Discourse.

Ich werde ein detailliertes Thema darüber schreiben, was genau ich getan habe, um dies zum Laufen zu bringen, mit genügend Details, damit jemand dies ohne Programmierung oder Haareraufen aktivieren kann. Der wirkliche Vorteil dieser Methode ist, dass man mit SES keinen E-Mail-Server für die Domain einrichten muss. Dies gibt Ihnen die Freiheit, einen völlig unabhängigen Mailserver für Ihre Domain einzurichten.

Wenn Sie daran interessiert sind, wie es gemacht wurde, schauen Sie wieder vorbei. Ich werde hier einen Link posten, wenn es fertig ist (in ein paar Tagen).

3 „Gefällt mir“

Zusätzlich werden wir, @dltj, den email-Parameter bald aus der Route admin/email/handle_mail entfernen. Sie müssen den E-Mail-Text als Base64-kodierten String in einem email_encoded-Parameter an diese Route senden.

3 „Gefällt mir“

encoding war eines der Probleme, die ich nicht lösen konnte.
Ich konnte die kodierte E-Mail nicht zum Laufen bringen.

  1. @martin, gemäß Ihrem Beitrag ist email_encoded falsch. Ich habe beides versucht, email_encoded wirft einen Fehler, aber encoded_email gibt immer noch die Warnung aus.

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',

  1. Muss ich meine eingehende E-Mail in reinen Text umwandeln (was ich gerade tue) und zurück in Base64 kodieren, oder ist es wahrscheinlich, dass das, was AWS SES im Bucket speichert, bereits Base64 ist? Wenn ich den E-Mail-Body so übergebe, wie SES ihn gespeichert hat, erscheint er nicht im Protokoll der empfangenen E-Mails. Der POST-Befehl gibt keinen Fehler zurück, nur die Warnung. Im Grunde stecke ich hier fest.
    Bitte verzögern Sie die Entfernung des email-Schlüssels, bis wir dies zum Laufen bringen können.

selbst wenn ich in Klartext dekodiere und wieder in Base64 kodieren, erhalte ich immer noch die Warnung und die E-Mail wird nicht als empfangen angezeigt :roll_eyes:

      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)

noch einmal, wenn ich Klartext mit „emal“ sende, wird dieselbe Nachricht empfangen und verarbeitet.
Sie sagen immer „streng“, bedeutet das etwas? Nodejs/JS hat nur zwei Base64-Varianten „base64“ „base64url“.

Warum gibt die API immer noch die Warnung aus, wenn ich encoded_email verwende, selbst wenn ich einen Fehler bei der Kodierung habe.

1 „Gefällt mir“

Ich habe curl ausprobiert und es mag encoded_email nicht, aber es war in Ordnung mit email_encoded…arrgh, also ist die Warnung falsch!

Also habe ich meinen Code noch einmal mit email_encoded ausprobiert, aber diesmal habe ich ein Base64-Encode-Modul anstelle des integrierten verwendet und es funktioniert, puh!

2 „Gefällt mir“

Ah! Ich sehe das jetzt. Danke für den Hinweis, @martin — ich werde das auf meine To-do-Liste setzen.

2 „Gefällt mir“

Wow, das ist total mein Fehler (die Warnung falsch zu machen), ich werde eine kleine PR machen, um das zu beheben.

2 „Gefällt mir“

Dieser PR wird es beheben und sollte später heute zusammengeführt werden DEV: Fix typo for email encoded by martin-brennan · Pull Request #15577 · discourse/discourse · GitHub.

2 „Gefällt mir“

Das ist großartig, danke fürs Teilen.
Wir haben die veraltete, vor-base64-Version drei Jahre lang auf Lambda verwendet und sie ist vor einem Monat ausgefallen.

Wir können SMTP :25 nicht verwenden, da wir uns hinter einem cloudflared tunnel befinden und warum mit Port 25 herumspielen, wenn Amazon mit SES so gute Arbeit leistet, um den Lärm zu blockieren?

Wie auch immer, ich bin am Ende meiner Kräfte.
Der Code funktioniert mit Postman und Python, die auf einem lokalen Computer ausgeführt werden. Etwas scheint kaputt zu gehen, wenn es von AWS Lambda mit identischem Code gesendet wird.

Forbidden
UTF-8

Nun, ich habe mein Problem gelöst -
obwohl die Anfragen übergeben wurden, verträgt sich der ‘Bot-Kampfmodus’ NICHT gut mit der Discourse-API. Vielleicht wegen der Base64-Kodierung?

Ich hoffe, das erspart jemandem 72 Stunden seines Lebens:
Schalten Sie dies AUS:

2 „Gefällt mir“