رمز نقطة نهاية مستلم البريد لـ AWS SES / AWS Lambda؟

أعتقد أن أفضل طريقة لتنفيذ مستقبل البريد (نظرًا لأنني أستخدم بالفعل AWS SES) هي استخدام واجهة برمجة التطبيقات (API) عبر وظيفة Lambda لاستدعاء نقطة النهاية API /admin/email/handle_mail

هناك دروس تعليمية حول كيفية إرسال البريد إلى SES وتمريره عبر S3 إلى وظيفة Lambda. توجد وظائف جاهزة بلغة Node.js لالتقاط هذا البريد وإعادة توجيهه إلى خادم بريد.

ولكن بدلاً من القيام بذلك، يبدو أنه من الأفضل استدعاء واجهة برمجة التطبيقات مباشرة من وظيفة Lambda وتمرير البريد الإلكتروني. (لا أقوم بصيانة خادم بريد محلي خاص بي ولا أرغب في استخدام الاستقصاء على أي حال).

لذلك، يمكنني الاستعانة ببعض المساعدة إما من خلال وثائق أكثر تفصيلاً (لا توجد صفحات وثائق على حد علمي) حول استدعاء نقطة النهاية هذه وما الذي يجب إرساله بالضبط (هل هي نقطة نهاية REST؟ هل هناك بديل لـ WebSocket؟ تنسيق ما يجب إرساله؟) أو بعض التعليمات البرمجية للمشاركة. هل ما زلت بحاجة إلى تشغيل هذه الحاوية الإضافية؟
Configure direct-delivery incoming email for self-hosted sites with Mail-Receiver (هذه المشاركة عمرها 6 سنوات الآن)

يبدو أن شخصًا ما قام بذلك باستخدام Python https://meta.discourse.org/t/how-to-update-mail-receiver-to-the-release-version/133491/7؟ ولكن لا توجد تفاصيل وسأحتاج إلى القيام بذلك بلغة Node/JavaScript.

هل لدى أي شخص وظيفة JavaScript عاملة يمكنه مشاركتها؟ شكرًا.

إعجابَين (2)

@dltj هنا. لا يمكنني المساعدة في Node/JavaScript، ولكن يمكنني تقديم بعض التعليمات البرمجية الزائفة في شكل Python الذي أستخدمه لتحقيق ذلك:

""" AWS Lambda لاستقبال الرسالة من SES ونشرها على 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):
    """تسجيل سمات الكائن السياقي المستلم من مشغل Lambda"""
    LOGGER.info(
        "الوقت المتبقي (مللي ثانية): %s.  مسار السجل: %s.  مجموعة السجلات: %s.  حدود الذاكرة (ميجابايت): %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):
    """ التعامل مع حدث تسليم 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(
            "معالجة SES مع mID %s (%s) من %s",
            mailMessageId,
            sesMessageId,
            mailSender,
        )

        deliveryRecipients = mail["destination"]
        for recipientEmailAddress in deliveryRecipients:
            LOGGER.info("مستلم التسليم: %s", recipientEmailAddress)

            s3resource = boto3.resource("s3")
            bucket = s3resource.Bucket(EMAIL_S3_BUCKET)
            obj = bucket.Object(sesMessageId)
            LOGGER.info("الحصول على %s/%s", EMAIL_S3_BUCKET, sesMessageId)
            post_data = {"email": obj.get()["Body"].read()}

            LOGGER.info("النشر إلى %s", DISCOURSE_SITE)
            r = requests.post(
                DISCOURSE_URL, headers=DISCOURSE_API_HEADERS, data=post_data
            )
            if r.status_code == 200:
                LOGGER.info("تم قبول البريد بواسطة Discourse: %s", DISCOURSE_SITE)
                obj.delete()
                processed = True
            else:
                LOGGER.error(
                    "تم رفض البريد بواسطة %s (%s): %s",
                    DISCOURSE_SITE,
                    r.status_code,
                    r.content,
                )

    return processed

أنا أستخدم Serverless لإدارة نشر وتحديث Lambda. في حال كنت ترغب في اتباع مسار مماثل، فلا تتردد في البدء بملف 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

شكرًا، بداية جيدة حتى لو لم تكن بلغة جافاسكريبت.

  1. كيف يمكن إنشاء مفتاح API (مشفر)؟ هل يحتاج إلى تشفير؟
  2. هل أحتاج إلى تحديد “تم تمكين الاستقصاء اليدوي” رسائل البريد الإلكتروني الفورية باستخدام واجهة برمجة التطبيقات للردود على البريد الإلكتروني. للحصول على نقطة نهاية واجهة برمجة التطبيقات لتكون نشطة أم أنها كذلك افتراضيًا؟
  3. هل هناك أي شيء يجب الانتباه إليه فيما يتعلق بأذونات AWS أو قاعدة SES؟

لا تحتاج المفتاح إلى التشفير. رمز البنية التحتية لمشروعنا موجود في مساحة مشتركة، لذلك لم أرغب في ترميز المفتاح بشكل ثابت في المصدر.

لدي “استطلاع يدوي ممكّن” على موقعي على الرغم من أنني لا أتذكر تحديدًا ما يفعله ذلك.

لا يوجد شيء غير عادي يمكنني تذكره فيما يتعلق بأذونات AWS بخلاف الإرسال والقراءة/الكتابة إلى الحاوية المؤقتة.

حظا طيبا!

هل يوجد مكان في واجهة المستخدم الرسومية على الويب حيث يمكن إنشاء مفتاح واجهة برمجة التطبيقات (API key)؟ لا يمكنني العثور على أي مكان في الإعدادات. ربما يمكنك تعيينه إلى ما تريده كمتغير بيئة (environment variable) عند البناء؟ على أي حال، ليس لدي أي فكرة عن كيفية إنشاء مفتاح واجهة برمجة التطبيقات.

مفتاح واجهة برمجة تطبيقات Discourse؟ يتم إنشاؤه في واجهة المسؤول على https://{discourse_url}/admin/api/keys

إعجاب واحد (1)

نعم، رأيت ذلك ولكني لم أعتقد أنه صحيح لأن “granular” لم يكن لديه إذن handle_email وكنت غير متأكد من المستخدم الذي يجب اختياره أو جميع المستخدمين.

لذلك، قمت بإنشاء مفتاح للمستخدم “system” بصلاحية عامة. هل هذا ما فعلته؟

حسنًا، كنت أرغب في استخدام تطبيق “postman” الخاص بي للتعلم/التجربة ولكن بدون وثائق، لا أعرف ما الذي يجب إرساله بتنسيقات وما هي وكيفية تمرير المفتاح. إذا كنت أكثر دراية بلغة بايثون، ربما يمكنني فهم ذلك من خلال النظر إلى الكود الخاص بك.

أعلم أن هذا مشروع مفتوح المصدر، لذا فمن المفهوم أنه لا توجد وثائق حقيقية حول استخدام واجهة برمجة تطبيقات handle_mail، فقط بعض المساعدة من أشخاص مثلك… شكرًا!

إنه ليس غير موثق لأنه مفتوح المصدر، بل هو غير موثق لأنه لم يطلب أحد (أو لم يطلب الكثيرون).

اقتراحات بأنك تريد إرسال البريد الإلكتروني المشفر عبر POST في شيء يسمى email_encoded. يوضح routes.rb أنه POST والمسار، وهو https://discourse.example.com/admin/email/handle_mail. حصلت عليه من قالب عينة مستلم البريد (وأوصي بأن تستخدمه فقط، كما لو كنت قد فعلت ذلك، لكانت قد انتهيت بالفعل) ولكنه موجود أيضًا في discourse/config/routes.rb at main · discourse/discourse · GitHub (والذي لا يوضح على الفور ما يحدث إلا إذا كنت تفهم rals).

إعجاب واحد (1)

“قالب عينة مستلم البريد”
أين هو؟ في المستودع؟ https://github.com/discourse/discourse؟

لقد تمكنت من فهم الأمر تقريبًا من كود بايثون

نجاح بعد جهد يومين!

@dltj تمكنت من إعادة كتابة الكود الخاص بك إلى nodejs باستخدام aws-sdk ووحدات get. شكراً جزيلاً.

كانت هناك عدة عقبات على طول الطريق، من AWS (ses,IAM,S3,lambda)، والعبث بالـ serverless، والحصول على استدعاء API بشكل صحيح، إلى ضبط إعدادات discourse بشكل مناسب.

ما سأفعله هو كتابة موضوع مفصل حول ما فعلته بالضبط للحصول على هذا العمل، بتفاصيل كافية تمكن شخصًا ما من تمكينه دون الحاجة إلى أي برمجة أو شد الشعر. الميزة الحقيقية لهذه الطريقة هي أنه باستخدام ses، لا يحتاج المرء إلى إعداد خادم بريد إلكتروني للنطاق. هذا يترك لك حرية إعداد خادم بريد إلكتروني مستقل تمامًا لنطاقك.

إذا كنت مهتمًا بكيفية القيام بذلك، فتحقق مرة أخرى، سأنشر رابطًا هنا عندما يكون جاهزًا (بعد بضعة أيام).

3 إعجابات

بالإضافة إلى ذلك، @dltj سنقوم بإزالة المعلمة email من المسار admin/email/handle_mail قريبًا، تحتاج إلى إرسال نص البريد الإلكتروني كسلسلة مشفرة بـ base64 في معلمة email_encoded إلى هذا المسار.

3 إعجابات

كان الترميز إحدى المشكلات التي لم أتمكن من حلها
لم أتمكن من جعل البريد الإلكتروني المشفر يعمل

  1. @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',

  1. هل أحتاج إلى تحويل بريدي الوارد إلى نص عادي (وهو ما أفعله الآن) والعودة إلى base64 أم من المحتمل أن ما يخزنه AWS Ses في الحاوية هو بالفعل base64؟ إذا مررت نص البريد الإلكتروني كما حفظه SES، فلن يظهر في سجل البريد المستلم. يعرض الأمر POST لا خطأ فقط التحذير. لذا أنا عالق هنا بشكل أساسي.
    يرجى تأخير إزالة مفتاح email حتى نتمكن من جعل هذا يعمل.

حتى لو قمت بفك التشفير إلى نص عادي وأعدت التشفير إلى base64، ما زلت أحصل على التحذير ولا يظهر البريد الإلكتروني كبريد مستلم :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)

مرة أخرى، إذا أرسلت نصًا عاديًا مع “emal”، يتم استلام الرسالة نفسها ومعالجتها.
هل تقول “صارم” يعني أي شيء؟ Nodejs/JS لديها نكهتان فقط من base64 “base64” “base64url”.
لماذا تستمر واجهة برمجة التطبيقات في إعطاء التحذير إذا استخدمت encoded_email حتى لو كان لدي خطأ في الترميز.

إعجاب واحد (1)

لقد جربت curl ولم يعجبه encoded_email ولكنه كان بخير مع email_encoded… اللعنة إذن التحذير غير صحيح!

لذا مرة أخرى جربت الكود الخاص بي ولكن مع email_encoded ولكن هذه المرة استخدمت وحدة تشفير base64 بدلاً من الوحدة المضمنة وهي تعمل الحمد لله!

إعجابَين (2)

آه! أنا أرى ذلك الآن. شكرًا لك على التنبيه، @martin — سأضيف هذا إلى قائمة مهامي.

إعجابَين (2)

واو، هذا خطئي تمامًا (في إفساد التحذير)، سأقوم بعمل طلب سحب بسيط لإصلاحه.

إعجابَين (2)

هذا الطلب سيصلحه، ويجب دمجه لاحقًا اليوم DEV: Fix typo for email encoded by martin-brennan · Pull Request #15577 · discourse/discourse · GitHub.

إعجابَين (2)

هذا رائع، شكراً لك على المشاركة.
كنا نستخدم الإصدار القديم الذي تم إهماله قبل ترميز base64 لمدة ثلاث سنوات على Lambda وانقطع قبل شهر.

لا يمكننا استخدام SMTP :25 لأننا نستضيف خلف نفق cloudflared ولماذا نلعب بالمنفذ 25 إذا كانت أمازون تقوم بعمل جيد في حظر الضوضاء باستخدام SES؟

على أي حال، لقد نفد صبري.
الكود يعمل مع Postman و python الذي يتم تنفيذه على جهاز كمبيوتر محلي. يبدو أن شيئًا ما يتم إفساده عند إرساله من AWS Lambda بنفس الكود.

Forbidden
UTF-8

حسنًا، لقد قمت بحل مشكلتي -
على الرغم من أن الاستعلامات تم تمريرها، فإن ‘وضع مكافحة الروبوت’ لا يعمل بشكل جيد مع واجهة برمجة تطبيقات Discourse. ربما بسبب الترميز base64؟

آمل أن يوفر هذا على شخص ما 72 ساعة من حياته:
قم بإيقاف تشغيل هذا:

إعجابَين (2)