Estoy pensando que la mejor manera de implementar el receptor de correo (ya que ya uso AWS SES) es usar la API a través de una función lambda para llamar al punto final de la API /admin/email/handle_mail.
Existen tutoriales sobre cómo llevar el correo a SES y pasarlo a través de S3 a una función lambda. Hay algunas funciones prefabricadas de nodejs para tomar ese correo y enviarlo a un servidor de correo.
Pero en lugar de hacer eso, parece mejor simplemente llamar a la API directamente desde una función lambda pasando el correo. (No mantengo mi propio servidor de correo local y de todos modos no quiero usar sondeos).
Así que podría usar un poco de ayuda, ya sea con documentación más detallada (no hay páginas de documentación, hasta donde sé) sobre cómo llamar a ese punto final y qué enviar exactamente (¿es un punto final REST? ¿hay una alternativa de websocket? ¿formato de lo que se debe enviar?) o algún código para compartir. ¿Todavía necesito ejecutar este contenedor adicional? Configure direct-delivery incoming email for self-hosted sites with Mail-Receiver (esa publicación tiene ahora 6 años)
@dltj aquí. No puedo ayudar con Node/JavaScript, pero puedo ofrecer algo de pseudocódigo en forma del Python que estoy usando para lograr esto:
""" AWS Lambda para recibir mensajes de SES y publicarlos en 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 los atributos del objeto de contexto recibido del invocador de Lambda"""
LOGGER.info(
"Tiempo restante (ms): %s. Flujo de registro: %s. Grupo de registro: %s. Límites de 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):
""" Maneja el evento de entrega de 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(
"Procesando un SES con mID %s (%s) de %s",
mailMessageId,
sesMessageId,
mailSender,
)
deliveryRecipients = mail["destination"]
for recipientEmailAddress in deliveryRecipients:
LOGGER.info("Destinatario de entrega: %s", recipientEmailAddress)
s3resource = boto3.resource("s3")
bucket = s3resource.Bucket(EMAIL_S3_BUCKET)
obj = bucket.Object(sesMessageId)
LOGGER.info("Obteniendo %s/%s", EMAIL_S3_BUCKET, sesMessageId)
post_data = {"email": obj.get()["Body"].read()}
LOGGER.info("Publicando en %s", DISCOURSE_SITE)
r = requests.post(
DISCOURSE_URL, headers=DISCOURSE_API_HEADERS, data=post_data
)
if r.status_code == 200:
LOGGER.info("Correo aceptado por Discourse: %s", DISCOURSE_SITE)
obj.delete()
processed = True
else:
LOGGER.error(
"Correo rechazado por %s (%s): %s",
DISCOURSE_SITE,
r.status_code,
r.content,
)
return processed
Estoy usando Serverless para gestionar el despliegue y mantenimiento de la Lambda. En caso de que quieras seguir una ruta similar, puedes empezar con este archivo serverless.yml:
¿Cómo se crea esa clave de API (cifrada)? ¿Necesita ser cifrada?
¿Necesito marcar “manual polling enabled” (activar sondeo manual) en Push emails using the API for email replies (correos electrónicos push usando la API para respuestas de correo electrónico) para que el punto final de la API esté activo o está activo por defecto?
¿Hay algo a lo que deba prestar especial atención en términos de permisos de AWS o reglas de SES?
La clave no necesita estar cifrada. El código de infraestructura de nuestro proyecto está en un espacio compartido, por lo que no quería codificar la clave en el código fuente.
Tengo “sondeo manual habilitado” en mi sitio, aunque no recuerdo específicamente qué hace.
Nada inusual que pueda recordar con los permisos de AWS más allá de enviar y leer/escribir en el bucket temporal.
¿Hay algún lugar en la interfaz de usuario web donde se genere la clave de API? No encuentro ningún lugar en la configuración. ¿Quizás simplemente la configuras a lo que quieras como variable de entorno cuando compilas? De todos modos, no tengo ni idea de cómo generar una clave de API.
Sí, vi eso, pero no pensé que fuera correcto porque “granular” no tenía permiso de handle_email y no estaba seguro de qué usuario o todos los usuarios elegir.
Así que he generado una clave para el usuario “system” con permiso global. ¿Es eso lo que hiciste?
Bueno, quería usar mi aplicación “postman” para aprender/probar, pero sin documentación, no sé qué POSTear en qué formato(s) y cómo pasar la clave. Si estuviera más familiarizado con python, tal vez podría entender esto mirando tu código.
Sé que este es un proyecto de código abierto, por lo que es comprensible que no haya documentación real sobre el uso de la API de handle_mail, solo alguna ayuda de personas como tú… ¡gracias!
No está sin documentar porque es de código abierto, está sin documentar porque nadie (o no muchos) lo han pedido.
Sugerencias de que quieres POSTear el correo codificado en un email_encoded thingy. routes.rb muestra que es un POST y la ruta, que es https://discourse.example.com/admin/email/handle_mail. Lo obtuve de la plantilla de ejemplo del receptor de correo (y te recomiendo que simplemente la uses, ya que si lo hubieras hecho, ya habrías terminado) pero también está en discourse/config/routes.rb at main · discourse/discourse · GitHub (lo cual no está inmediatamente claro qué está sucediendo a menos que entiendas rals).
@dltj Pude reescribir tu código en nodejs usando el aws-sdk y los módulos ‘got’. Muchas gracias.
Hubo varios obstáculos en el camino, desde AWS (ses, IAM, S3, lambda), jugando con serverless, haciendo que la llamada a la API fuera correcta, hasta configurar adecuadamente los ajustes de discourse.
Lo que voy a hacer es escribir un tema detallado sobre exactamente lo que hice para que esto funcione, con suficiente detalle como para que alguien pueda habilitarlo sin tener que codificar o arrancarse los pelos. La verdadera ventaja de este método es que al usar ses, NO es necesario configurar un servidor de correo electrónico para el dominio. Esto te deja libre para configurar un servidor de correo totalmente independiente para tu dominio.
Si estás interesado en cómo se hizo, vuelve a consultar, publicaré un enlace aquí cuando esté listo (en unos días).
Además, @dltj pronto eliminaremos el parámetro email de la ruta admin/email/handle_mail, deberás enviar el cuerpo del correo electrónico como una cadena codificada en base64 en un parámetro email_encoded a esta ruta.
La codificación fue uno de los problemas que no pude resolver.
No pude hacer que el correo electrónico codificado funcionara.
@martin, según tu publicación, email_encoded es incorrecto. He probado ambos, email_encoded genera un error, pero encoded_email todavía da la advertencia.
body: 'advertencia: el parámetro email está obsoleto. Todas las solicitudes POST a esta ruta deben enviarse con un parámetro encoded_email codificado en estricto base64 en su lugar. El email ha sido recibido y está en cola para su procesamiento',
¿Necesito convertir mi correo entrante a texto plano (lo que estoy haciendo ahora) y volver a base64, o es probable que lo que AWS Ses almacena en el bucket ya esté en base64? Si paso el cuerpo del correo electrónico tal como lo guardó SES, no aparece en el registro de correos recibidos. El comando POST no devuelve ningún error, solo la advertencia. Así que, básicamente, estoy atascado aquí.
Por favor, retrasa la eliminación de la clave email hasta que podamos hacer que esto funcione.
una vez más, si envié texto plano con “emal”, el mismo mensaje se recibe y se procesa.
Sigues diciendo “estricto”, ¿eso significa algo? Nodejs/JS solo tiene dos sabores base64, “base64” y “base64url”.
¿Por qué la API sigue dando la advertencia si uso encoded_email, incluso si tengo un error en la codificación?
Intenté usar curl y no le gusta encoded_email, pero estaba bien con email_encoded… ¡qué frustración! ¡Así que la advertencia es incorrecta!
Así que, de nuevo, probé mi código pero con email_encoded, pero esta vez usé un módulo de codificación base64 en lugar del integrado y está funcionando. ¡Uf!
Esto es genial, gracias por compartir.
Usamos la versión obsoleta anterior a base64 durante tres años en Lambda y se desactivó hace un mes.
No podemos usar el puerto SMTP 25 ya que estamos alojados detrás de un túnel cloudflared y, ¿por qué jugar con el puerto 25 si Amazon hace un buen trabajo bloqueando el ruido con SES?
De todos modos, estoy al final de mi cordura.
El código funciona con Postman y Python ejecutándose en una computadora local. Algo parece arruinarse cuando se envía desde AWS Lambda con el código idéntico.
Forbidden
UTF-8
Bueno, resolví mi problema:
incluso si las consultas se estaban pasando, el ‘modo de lucha contra bots’ NO funciona bien con la API de Discourse. ¿Quizás por la codificación base64?
Espero que esto le ahorre a alguien 72 horas de su vida:
Desactiva esto: