Codice endpoint ricevitore email AWS SES / AWS Lambda?

Sto pensando che il modo migliore per implementare il ricevitore di posta (dato che uso già AWS SES) sia utilizzare l’API tramite una funzione lambda per chiamare l’endpoint API /admin/email/handle_mail

Ci sono tutorial su come inviare posta a SES e passarla tramite S3 a una funzione lambda. Ci sono alcune funzioni nodejs pre-costruite per prendere quell’email e inoltrarla a un server di posta.

Ma piuttosto che fare ciò, sembra meglio chiamare direttamente l’API da una funzione lambda passando l’email. (Non gestisco il mio server di posta locale e comunque non voglio usare il polling).

Quindi potrei usare un piccolo aiuto, sia tramite documentazione più dettagliata (nessuna pagina di documentazione per quanto ne so) su come chiamare quell’endpoint e cosa inviare esattamente (è un endpoint REST? c’è un’alternativa websocket? formato di ciò che si deve inviare?) o qualche codice da condividere. Devo ancora eseguire questo container aggiuntivo?
Configure direct-delivery incoming email for self-hosted sites with Mail-Receiver (quel post ha ora 6 anni)

Qualcuno apparentemente ha fatto questo con Python Update mail-receiver to the release version - #7 by dltj? ma non ci sono dettagli e dovrò farlo in node/javascript.

Qualcuno ha una funzione javascript funzionante da condividere? Grazie

2 Mi Piace

@dltj qui. Non posso aiutarti con Node/JavaScript, ma posso offrire un po’ di pseudo-codice sotto forma di Python che sto usando per realizzarlo:

""" AWS Lambda per ricevere messaggi da SES e pubblicarli su 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):
    """Registra gli attributi dell'oggetto context ricevuto dall'invocatore Lambda"""
    LOGGER.info(
        "Tempo rimanente (ms): %s.  Stream di log: %s.  Gruppo di log: %s.  Limiti memoria (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):
    """ Gestisce l'evento di consegna 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(
            "Elaborazione di un SES con mID %s (%s) da %s",
            mailMessageId,
            sesMessageId,
            mailSender,
        )

        deliveryRecipients = mail["destination"]
        for recipientEmailAddress in deliveryRecipients:
            LOGGER.info("Destinatario della consegna: %s", recipientEmailAddress)

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

            LOGGER.info("Invio a %s", DISCOURSE_SITE)
            r = requests.post(
                DISCOURSE_URL, headers=DISCOURSE_API_HEADERS, data=post_data
            )
            if r.status_code == 200:
                LOGGER.info("Post accettato da Discourse: %s", DISCOURSE_SITE)
                obj.delete()
                processed = True
            else:
                LOGGER.error(
                    "Post rifiutato da %s (%s): %s",
                    DISCOURSE_SITE,
                    r.status_code,
                    r.content,
                )

    return processed

Sto usando Serverless per gestire il deployment e la manutenzione della Lambda. Nel caso in cui tu voglia seguire un percorso simile, sentiti libero di iniziare con questo file 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

Grazie, buon inizio anche se non è js.

  1. Come si crea quella chiave API (crittografata)? Deve essere crittografata?
  2. Devo selezionare “Abilita polling manuale” Push emails using the API for email replies. per attivare l’endpoint API o è attivo per impostazione predefinita?
  3. C’è qualcosa a cui prestare particolare attenzione in termini di autorizzazioni AWS o regole SES?

La chiave non ha bisogno di essere crittografata. Il codice dell’infrastruttura per il nostro progetto si trova in uno spazio condiviso, quindi non volevo codificare la chiave sorgente.

Ho “il polling manuale abilitato” sul mio sito, anche se non ricordo specificamente cosa faccia.

Niente di insolito che io possa ricordare con le autorizzazioni AWS oltre all’invio e alla lettura/scrittura nel bucket temporaneo.

Buona fortuna!

C’è un posto nell’interfaccia web dove si genera la chiave API? Non riesco a trovare nulla nelle impostazioni. Forse la imposti semplicemente a ciò che desideri come variabile d’ambiente quando compili? Comunque, non ho idea di come generare una chiave API.

La chiave API di Discourse? Viene generata nell’interfaccia di amministrazione all’indirizzo https://{discourse_url}/admin/api/keys

1 Mi Piace

Sì, l’ho visto ma non pensavo fosse giusto perché “granular” non aveva il permesso handle_email ed ero incerto se scegliere un utente o tutti gli utenti.

Quindi ho generato una chiave per l’utente “system” con permesso globale. È quello che hai fatto?

Beh, volevo usare la mia app “postman” per imparare/provarla ma senza documentazione non so cosa inviare in quale/i formato/i e come passare la chiave. Se avessi più familiarità con python potrei forse capirlo guardando il tuo codice.

So che questo è un progetto open source, quindi è comprensibile che non ci sia una vera documentazione sull’uso dell’API handle_mail, solo un po’ di aiuto da persone come te… grazie!

Non è non documentato perché è open source, è non documentato perché nessuno (o non molti) l’hanno chiesto.

Suggerimenti che vuoi inviare tramite POST l’email codificata in una cosa email_encoded. routes.rb mostra che è un POST e il percorso, che è https://discourse.example.com/admin/email/handle_mail. L’ho preso dal modello di esempio del ricevitore di posta (e ti consiglio di usarlo, perché se l’avessi fatto, avresti già finito) ma è anche in discourse/config/routes.rb at main · discourse/discourse · GitHub (che non è immediatamente chiaro cosa stia succedendo a meno che tu non capisca rals).

1 Mi Piace

“modello di esempio per la ricezione di email”
Dov’è situato? Nel repository? GitHub - discourse/discourse: A platform for community discussion. Free, open, simple. ?

Sono riuscito a capirlo quasi interamente dal codice Python

Successo dopo qualche giorno di sforzo!

@dltj Sono riuscito a riscrivere il tuo codice in nodejs usando aws-sdk e moduli get. Grazie mille.

Ci sono stati diversi ostacoli lungo il percorso, da AWS (ses, IAM, S3, lambda), a pasticciare con serverless, a ottenere la chiamata API corretta, fino a ottenere le impostazioni di discourse corrette.

Quello che farò è scrivere un argomento dettagliato su esattamente cosa ho fatto per far funzionare questo, abbastanza dettagliato che qualcuno potrebbe abilitarlo senza dover fare alcun tipo di codifica o tirarsi i capelli. Il vero vantaggio di questo metodo è che utilizzando ses NON è necessario configurare un server di posta elettronica per il dominio. Questo ti lascia libero di configurare un server di posta totalmente indipendente per il tuo dominio.

Se sei interessato a come è stato fatto, controlla di nuovo, posterò un link qui quando sarà pronto (tra qualche giorno).

3 Mi Piace

Aggiungendo a questo, @dltj rimuoveremo presto il parametro email dal percorso admin/email/handle_mail, è necessario inviare il corpo dell’email come stringa codificata in base64 in un parametro email_encoded a questo percorso.

3 Mi Piace

encoding era uno dei problemi che non riuscivo a risolvere.
Non riuscivo a far funzionare l’email codificata.

  1. @martin, secondo il tuo post, email_encoded è errato. Ho provato entrambi, email_encoded genera un errore, ma encoded_email dà ancora l’avviso.

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. Devo convertire la mia posta in arrivo in testo normale (cosa che sto facendo ora) e riconvertirla in base64 o è probabile che ciò che AWS Ses memorizza nel bucket sia già in base64? Se passo il corpo dell’email come lo ha salvato SES, non appare nel registro delle email ricevute. Il comando post non restituisce errori, solo l’avviso. Quindi, fondamentalmente, sono bloccato qui.
    Si prega di ritardare la rimozione della chiave email finché non saremo in grado di farlo funzionare.

Anche se decodifico in testo normale e ricodifico in base64, ricevo ancora l’avviso e l’email non viene visualizzata come ricevuta :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)

Ancora una volta, se invio testo normale con “emal”, lo stesso messaggio viene ricevuto ed elaborato.
Continui a dire “strict”, questo significa qualcosa? Nodejs/JS ha solo due varianti di base64: “base64” e “base64url”.

Perché l’API continua a dare l’avviso se uso encoded_email anche se ho un errore nella codifica.

1 Mi Piace

Ho provato curl e non gli piace encoded_email ma andava bene con email_encoded… uffa, quindi l’avviso è errato!

Quindi, ancora una volta ho provato il mio codice ma con email_encoded ma questa volta ho usato un modulo di codifica base64 invece di quello integrato e funziona, meno male!

2 Mi Piace

Ah! Ora capisco. Grazie per l’avviso, @martin — lo aggiungerò alla mia lista di cose da fare.

2 Mi Piace

Wow, è totalmente colpa mia (aver sbagliato l’avviso), farò una piccola PR per correggerlo.

2 Mi Piace

Questa PR risolverà il problema, dovrebbe essere unita più tardi oggi DEV: Fix typo for email encoded by martin-brennan · Pull Request #15577 · discourse/discourse · GitHub.

2 Mi Piace

Questo è fantastico, grazie per aver condiviso.
Abbiamo utilizzato la versione deprecata pre-base64 per tre anni su Lambda e si è bloccata un mese fa.

Non possiamo usare SMTP :25 poiché siamo ospitati dietro un tunnel cloudflared e perché giocare con la porta 25 se Amazon fa un ottimo lavoro bloccando il rumore con SES?

Comunque, sono alla fine della mia sanità mentale.

Il codice funziona con Postman e python eseguito su un computer locale. Qualcosa sembra corrompersi quando viene inviato da AWS Lambda con codice identico.

Forbidden
UTF-8

Bene, ho risolto il mio problema -
anche se le query venivano passate, la ‘modalità di lotta ai bot’ NON va d’accordo con l’API di Discourse. Forse a causa della codifica base64?

Spero che questo risparmi a qualcuno 72 ore della sua vita:
Disattiva questo:

2 Mi Piace