Skip to main content
AhaSend lets you send transactional email from AWS Lambda in Python through the HTTP API; this guide adds a locally testable handler for trusted, IAM-authorized invocations.

Prerequisites

Use a verified sending domain, your account ID, and a domain-scoped send-only API v2 key. Use sandbox mode for every test. Management tasks need a separate full API key. Use Python 3.13, a runtime supported by AWS Lambda. This example needs no third-party packages.

Prepare the Python Environment

Try a Sandbox Send

Set AHASEND_API_KEY, AHASEND_ACCOUNT_ID and AHASEND_FROM in your shell or local secret store. Use a send-only key and an address on your verified domain. Save this as quick_send.py:
quick_send.py
Run python quick_send.py in the activated environment. It prints acceptance statuses and exits with an error if sending fails. Sandbox acceptance does not deliver mail. The production version below adds input checks and a stable request key.

Add the Shared Sending Code

Save this as send_email.py beside the function file. It uses Python’s standard library, so this example needs no SDK package. Set AHASEND_API_KEY, AHASEND_ACCOUNT_ID, AHASEND_FROM, AHASEND_TO and SEND_TOKEN in the runtime environment. Use a long random SEND_TOKEN for requests from your trusted server. For the first run set AHASEND_TO=recipient@example.com and AHASEND_DELIVERY_MODE=sandbox; keep a sandbox credential in place while testing. Set live only when ready to deliver to your own approved recipients.
send_email.py
Only your server chooses the sender, recipient and message body. Look up a user’s verified address from your database before adapting this to a real signup flow. Keep that saved message unchanged when retrying an event_id; a new business event needs a new ID. See idempotency for time limits and uncertain outcomes. Record an internal event ID on failures without logging API keys or message bodies. Add request limits at the hosting layer.

Create the Lambda Handler

Save the following file as lambda_function.py. This handler accepts a direct Lambda invocation, with a JSON object containing event_id. Let IAM decide which backend roles may invoke it. If you later add a Function URL, use AWS_IAM authentication and adapt the request parsing; this file does not parse HTTP event envelopes.
lambda_function.py

Run It Locally

Export the environment variables named above, then run this in the same directory. A new test run needs a new event_id; retry a previous job with its original ID.
Expect queued or scheduled statuses. An invalid event or API failure raises an exception, so the caller can record the failed job. Before using asynchronous Lambda invocation, account for AWS retries and the AhaSend idempotency time limit; keep a durable job record.

Configure the Hosted Function

Package both Python files at the archive root and set the handler to lambda_function.lambda_handler with runtime python3.13. Give the function enough time for its 10-second HTTP request and startup. Provide outbound HTTPS access; a function inside a private VPC needs an appropriate network path to the public API. Keep secrets in AWS Secrets Manager and load them in your deployment’s secret-handling code. Follow AWS’s Python packaging steps when publishing. Use delivery webhooks to track the final outcome after acceptance. See Lambda in Node.js for the SDK version and Python SMTP for the SMTP alternative.