Provider integrations

How to Send Email Through AWS SES Without Storing AWS Credentials in Your App

Compare direct Amazon SES access with a narrow email operation that keeps AWS credentials outside the application runtime.

An application can send email through Amazon SES without storing an AWS access key in its own runtime. The usual choices are:

  1. Give the application a narrowly scoped AWS identity and let it call SES directly.
  2. Put a separate email service between the application and SES. The application authenticates to that service, which holds the AWS credential and sends the message.

The first option is often the simplest, especially for an application already running on AWS. The second changes the trust boundary: the application can request a supported email operation without receiving the AWS credential.

Direct SES access is a valid default

A direct integration looks like this:

~~~text Application -- AWS-authenticated SendEmail request --> Amazon SES ^ └── IAM role or restricted access key ~~~

If the application runs on EC2, ECS, Lambda, or another AWS workload, attach an IAM role with only the SES permissions it needs. The AWS SDK obtains temporary credentials from the workload environment and signs the request. The application does not need a long-lived access key in a .env file.

Outside AWS, the application may use an access key and secret access key supplied through its deployment platform or a dedicated secret manager. That is still a reasonable design when the application genuinely owns the SES integration. A secret manager controls delivery and rotation; it does not stop the running application from receiving the credential.

The normal direct setup is regional. Choose an SES Region, verify the sending domain or address there, configure DKIM and DMARC, and check whether the account is still in the SES sandbox. Production access and sending quotas are also regional. See AWS’s guides to SES Regions, verified identities, and Easy DKIM.

The application then uses the SES API or SMTP. With the API, it may send a simple message, a templated message, or a raw MIME message when it needs attachments or custom headers. SMTP credentials are SES-specific and are not interchangeable with ordinary AWS access keys. Read the SES sending API and SMTP guide.

An IAM policy should be limited to the required sending actions and, where practical, the intended verified identity. An application using structured sending may need ses:SendEmail; a library that submits raw MIME may also need ses:SendRawEmail. It should not also grant permission to create IAM users, change DNS, alter SES identities, or administer the AWS account. AWS’s SES IAM documentation describes the available controls.

What the application must still operate

Direct SES access does not remove the email work around the API call. The application still has to:

  • choose sender and recipient rules;
  • keep development, staging, and production configuration separate;
  • handle sandbox restrictions and sending limits;
  • distinguish an accepted SES request from final delivery;
  • process bounces, complaints, and delivery events;
  • decide whether a retry is safe.

If a network timeout occurs after the request was sent, the application may not know whether SES accepted it. Retrying blindly can send a duplicate. Record the application’s send intent and decide whether each message is safe to retry. For a password-reset or receipt email, a duplicate may be acceptable; for an operation that triggers a business action, an ambiguous result needs more careful handling.

SES also has account and Region sending limits. A request rejected for exceeding the daily quota or maximum sending rate was not queued for later delivery. The application needs bounded retry and back-pressure behavior rather than assuming SES will eventually send every rejected request. See AWS’s sending quota guidance.

An accepted SES request means SES accepted the message for processing. It does not mean the recipient’s mail server accepted it or that the user saw it. Record the SES MessageId and correlate later delivery, bounce, complaint, delay, reject, and rendering-failure events with the application message.

Sending through an SES relay instead

An email relay changes the request path:

~~~text Application -- authenticated email request --> Relay -- AWS-authenticated request --> SES Application <-- accepted result or error --- Relay <-- SES result or error --------- ~~~

The application proves its identity to the relay and sends the message fields the relay supports. The relay validates the request, selects its trusted Region and sender configuration, applies the AWS credential, calls SES, and returns an application-facing result.

This is different from putting the same AWS secret in another configuration variable. A secret manager delivers a credential to the workload. A relay performs a defined provider operation while keeping the provider credential at the relay boundary.

A relay should have a deliberate contract. It should say which fields it accepts, how it handles attachments and reply-to addresses, how it reports provider errors, and what happens in development and staging. It should not quietly become an unrestricted AWS API proxy just because several provider calls happen to share authentication plumbing.

The concrete email contract

A useful email relay authenticates every request with an application identity and checks a capability before provider execution. It validates recipients, subject line, body, attachments, reply-to values, size limits, and environment. It manages the sender identity instead of allowing an untrusted caller to select an arbitrary From address.

The response should distinguish validation failure, missing configuration, provider rejection, throttling, transport failure, and uncertain provider execution. A provider message ID means the provider accepted the message or that the relay captured it locally; it does not mean delivery to a recipient.

Message content is also sensitive. Avoid logging full reset links, one-time codes, attachments, or recipient lists unless the retention and access rules justify it. A relay can reduce provider-key exposure while still becoming a place where customer communications and personal data pass through.

Where Gateman fits

Gateman implements this relay model for its supported email capability. The capability and scope are both email.send, and the SES route is POST /send/ses. The caller supplies Gateman’s documented SES message shape, such as environment, to, subject, text, html, reply_to, and supported attachments.

A caller-supplied SES from value is rejected. Gateman controls the configured sender identity, so the request cannot silently choose another From address. Provider authentication is managed by Gateman; the application does not send AWS access keys, SigV4 headers, or SES authentication fields in the email payload.

The request path is:

~~~text Application → authenticates with its Gateman identity → sends the documented email payload to POST /send/ses → Gateman checks email.send → development/staging: validates and captures locally → production: calls SES with the configured AWS credential → returns the documented Gateman response ~~~

Development and staging validate and capture locally without contacting SES. Production contacts SES. The official PHP, Node.js, Python, and Go clients can supply the configured environment, allowing the application to use the same production-intended send code across environments.

The total supported HTTP request body is 4 MiB, including the complete JSON payload and Base64-encoded attachments. The individual message and attachment limits also apply. Gateman’s SES route is a purpose-built email operation, not a raw SES SDK passthrough; the application sends the documented Gateman shape and Gateman maps it to the provider’s configured sending path.

Optional email idempotency follows the documented contract. A valid X-GM-Idempotency-Key can activate email idempotency. Successful and definite-failure results are retained for 15 minutes. An unresolved provider-started outcome may remain protected longer, and a 409 mail_send_indeterminate does not prove that SES did nothing. Official Gateman clients do not automatically retry email sends.

Read the email capability reference and idempotency documentation for the exact request, response, capture, and retry contract.

Bounce and complaint handling

SES identity verification, DKIM, production access, quotas, consent, reputation, bounces, and complaints still apply when a relay holds the AWS credential. A relay cannot make a recipient mailbox accept a message.

For direct SES, use SNS feedback notifications or configuration-set event publishing. With Gateman Premium tracking, Gateman can receive the configured SES/SNS events, maintain its email status and suppression behavior, and correlate provider events to the application’s accepted send. This is an additional protection layer, not a replacement for SES controls or application consent and unsubscribe policy. See Webhooks and event handling.

Latency, availability, and back-pressure

The relay adds a network hop and makes its availability part of the send path. Bound timeouts, monitor provider latency, classify 4xx and 5xx responses, and handle provider rate limits without an unbounded retry loop. A relay should have capacity controls so a traffic spike does not cause a thundering herd against SES.

For an uncertain result, do not generate a new idempotency key and resend automatically. The application should record the logical send, consult the relay’s idempotency contract, and reconcile the result. In a user-facing request, it may be better to queue the message and show a pending state than to block on repeated provider calls.

Credential rotation also needs an owner. Direct access rotates a workload role or deployment secret; a relay rotates its configured AWS credential while preserving application identities. In both models, deleting a local copy is not the same as revoking the provider credential.

Which model should you choose?

Use direct SES access when the application runs with a suitable IAM role or secret-management setup and there is no strong reason to add a relay. Direct access is simpler when the team needs broad SES API coverage, raw MIME control, provider-native debugging, or the lowest possible path complexity.

Consider a purpose-built relay when several applications should use the same application-facing email contract, when non-AWS deployments should not receive AWS credentials, or when development and staging should use the same production-intended email code without sending real messages.

Neither model makes email delivery automatic. The important design decision is whether the application should possess an AWS identity, or only be authorized to request the specific email operation it needs.

← All articles