@dltj here. I can’t help with Node/JavaScript, but I can offer some pseudo-code in the form of the Python that I’m using to make this happen:
""" AWS Lambda to receive message from SES and post it to 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):
"""Log the attributes of the context object received from the Lambda invoker
"""
LOGGER.info(
"Time remaining (ms): %s. Log stream: %s. Log group: %s. Mem. limits(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):
""" Handle SES delivery event """
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(
"Processing an SES with mID %s (%s) from %s",
mailMessageId,
sesMessageId,
mailSender,
)
deliveryRecipients = mail["destination"]
for recipientEmailAddress in deliveryRecipients:
LOGGER.info("Delivery recipient: %s", recipientEmailAddress)
s3resource = boto3.resource("s3")
bucket = s3resource.Bucket(EMAIL_S3_BUCKET)
obj = bucket.Object(sesMessageId)
LOGGER.info("Getting %s/%s", EMAIL_S3_BUCKET, sesMessageId)
post_data = {"email": obj.get()["Body"].read()}
LOGGER.info("Posting to %s", DISCOURSE_SITE)
r = requests.post(
DISCOURSE_URL, headers=DISCOURSE_API_HEADERS, data=post_data
)
if r.status_code == 200:
LOGGER.info("Mail accepted by Discourse: %s", DISCOURSE_SITE)
obj.delete()
processed = True
else:
LOGGER.error(
"Mail rejected by %s (%s): %s",
DISCOURSE_SITE,
r.status_code,
r.content,
)
return processed
I’m using Serverless to manage the deployment and upkeep of the Lambda. In case you want to go a similar route, you’re welcome to start with this serverless.yml
file:
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