Configuration and Deployment
Environment variables
Lambda function configuration
Set these on the Lambda function’s configuration, for example:
$ aws lambda update-function-configuration \
--function-name email2webhook \
--environment '{
"Variables": {
"SES_S3_BUCKET": "email-inbox-123456789012-us-east-2-an",
"SES_S3_KEY_PREFIX": "inbound/",
"WEBHOOK_URL": "https://example.com/webhooks/email2webhook",
"WEBHOOK_SECRET": "replace-with-a-real-secret",
"WEBHOOK_FORMAT": "postal",
"WEBHOOK_SIGNATURE": "hmac"
}
}'
SES_S3_BUCKET— name of the S3 bucket where the receipt rule stores raw emails.SES_S3_KEY_PREFIX— optional object key prefix, only needed if the S3 action’s “Object key prefix” field was set to a non-empty value; it must match that value exactly, including the trailing slash if you want it to appear as a folder in the S3 console (e.g.inbound/), since S3 has no real folders — a/in the key is just a display convention (default: none).WEBHOOK_URL— URL the envelope is POSTed to. Required unlessWEBHOOK_DEBUG_URLis set (debug-only mode).WEBHOOK_DEBUG_URL— optional second URL that gets an identical, best-effort copy of the same signed request, sent beforeWEBHOOK_URL— for temporarily watching traffic (e.g. a debug endpoint) without touchingWEBHOOK_URL. Failures here (bad status, timeout, connection error) are logged and never raised, so a broken or slow debug endpoint can never block or fail the realWEBHOOK_URLrequest. IfWEBHOOK_URLisn’t set, the function runs in debug-only mode and sends only here.WEBHOOK_SECRET— shared secret used to sign the request body whenWEBHOOK_SIGNATURE=hmac(the default).WEBHOOK_FORMAT—postal(default) orses. See “Output formats” above.WEBHOOK_SIGNATURE—hmac(default) ornone. See “Signing” above.WEBHOOK_SIGNATURE_PREFIX— literal string prepended to the hex digest inX-Webhook-Signature-256whenWEBHOOK_SIGNATURE=hmac(default: none, just the hex digest).ATTACHMENT_REQUIRE_EXTENSIONS— comma-separated list of attachment extensions (with or without the leading dot, case-insensitive, e.g.xmlor.xml,.txt). If set, an email is only processed (and sent to the webhook at all) when it has at least one attachment matching one of these extensions; otherwise it’s discarded silently. Applies to bothWEBHOOK_FORMATvalues. Default: empty, no filtering.ATTACHMENT_ALLOW_EXTENSIONS— same syntax as above. If set, only matching attachments are included in thepostalformat’sattachmentslist (non-matching ones are dropped, but the email itself is still sent); has no effect on thesesformat, which embeds the raw email as-is. Default: empty, all attachments included.
Local testing only
Never set these in the deployed Lambda function; they exist only to run lambda_function.py directly on a workstation, without AWS credentials or a live webhook:
SES_LOCAL_TEST_EML_FILE— path to a local.emlfile; when set,get_raw_emailreads from this file instead of calling S3.SES_LOCAL_TEST_MESSAGE_ID—messageIdvalue used to build the mock SES event in the__main__block (default:local-test-message-id).
Deploying
make all installs the runtime dependencies listed in pyproject.toml’s [project.dependencies] into function/ (clean runs first, so no stray files from a previous build or a local test run end up bundled), then zips function/ into aws-lambda-email2webhook.zip. make clean removes the zip and everything under function/ except lambda_function.py, restoring it to its checked-in state. make dev creates a local .venv with the dev extras (ruff, mypy, boto3, requests, plus type stubs) for linting, type-checking and running the function locally — none of that ever gets bundled into the zip.
boto3 is deliberately not in [project.dependencies] — the Lambda Python runtime already provides it, adding it would only bloat the deployment package. It’s only listed under the dev extra, to have it available locally for type-checking and for running lambda_function.py directly (see “Local testing only” above).
The Makefile pins python3.14 to build the package, matching the Lambda runtime’s version, so there’s no risk of a compiled extension (requests’s charset_normalizer is one) being built against a mismatched Python/OS ABI.
Once you have the zip, upload it — the first time, create the function; afterwards, update its code:
$ aws lambda update-function-code \
--function-name email2webhook \
--zip-file fileb://aws-lambda-email2webhook.zip
Verifying the webhook signature
The receiving end must recompute hmac_sha256(WEBHOOK_SECRET, raw_request_body) and compare it (constant-time) against the value in X-Webhook-Signature-256. By default that value is just the hex digest, nothing to strip; if WEBHOOK_SIGNATURE_PREFIX is set, strip that literal string from the front before comparing.