Can I send email from AWS Lambda without Amazon SES?
Yes. A Lambda function can call any HTTPS API, and the Notix email API is one POST to /api/v1/emails with a Bearer token. There is no sandbox to request your way out of, no per-region identity verification and no SNS topic to wire for bounces: verify your domain once in the Notix dashboard, put an API key in the function's environment or Secrets Manager, and the handlers on this page send from the first invocation. The SES comparison page sets out honestly when SES itself is the better fit.
Why does my Lambda send the same email two or three times?
Because Lambda retried it. An asynchronous invocation that errors, including a timeout, is retried twice by default, and an SQS-triggered function that errors sees the message again once its visibility timeout expires. If the first attempt reached Notix and then the function timed out, the retries send again. Pass an idempotency key built from the event, such as order-4471-receipt: the same key with the same body returns the original email instead of sending a new one.
Where should the API key live?
A plain environment variable works and is encrypted at rest by Lambda, so it is fine for a first deploy. For production, keep the key in AWS Secrets Manager or SSM Parameter Store, add the AWS Parameters and Secrets Lambda Extension as a layer, and read the value once at module scope over the extension's localhost endpoint; it caches for five minutes by default, so warm invocations never call the secrets API. Give the function a sending-access key, which cannot read contacts or delete domains if it leaks.
My function times out calling the API, but the same code works locally.
Two usual causes. The default function timeout is 3 seconds, which a cold start plus a TLS handshake plus one API call can exceed; set 10 to 15 seconds. Or the function is attached to a VPC: then every outbound packet goes through that VPC, and without a NAT gateway in a public subnet, with the function in a private subnet, there is no route to the internet at all. A function that does not need VPC resources should not be in a VPC.
Should the handler throw when the send fails?
Only when a retry can help. A transport failure or a 429 RATE_LIMITED is worth throwing for, because Lambda's retry (or the SQS redelivery) will likely succeed a minute later. A 400 for an unverified from domain, a 403 for a key without sending access, or a 422 for a suppressed address will fail identically every time; log the code, return normally, and let the record leave the queue rather than burning retries and landing in a dead-letter queue for nothing.
Does a warm Lambda reuse the connection?
It reuses whatever you create outside the handler. Construct the Notix client at module scope, in Python at import time and in Go in init or a package-level var, and it lives for the life of the execution environment. Node's fetch keeps the TLS connection alive between warm invocations; creating the client inside the handler throws that away on every call and adds a handshake each time.