MailKite
Get your API key
All server guides
Enterprise mail services

Send application email with the Amazon SES SDK

Send a controlled SES message with the AWS JavaScript SDK, least-privilege credentials, and separate acceptance and delivery tracking.

Reviewed

Use the AWS SDK to send through your own SES account when the application needs direct AWS ownership of delivery. This walkthrough uses the SDK v3 SESClient and the classic SendEmail operation. It does not use the differently shaped SESv2 SendEmail request or a MailKite adapter.

Check the prerequisites

Complete sending identity setup first. You need a verified sender in the selected Region, an approved recipient, and an application IAM role with the necessary sending permission. Sandbox recipient restrictions still apply until production access is granted in that Region.

Use Node.js with ES modules and the official AWS client:

npm install @aws-sdk/client-ses

Provide AWS_REGION, SES_FROM, and SES_TEST_TO through runtime configuration. Use an approved role or the SDK’s standard credential chain rather than placing access keys in this source file. Both addresses must be real controlled/approved addresses before a live test; reserved documentation addresses are not test recipients.

Send one bounded test

Save as send-ses.mjs:

import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";

for (const name of ["AWS_REGION", "SES_FROM", "SES_TEST_TO"]) {
  if (!process.env[name]) throw new Error(`Missing ${name}`);
}

const ses = new SESClient({
  region: process.env.AWS_REGION,
  maxAttempts: 1,
});

const result = await ses.send(new SendEmailCommand({
  Source: process.env.SES_FROM,
  Destination: { ToAddresses: [process.env.SES_TEST_TO] },
  Message: {
    Subject: { Charset: "UTF-8", Data: "SES application integration test" },
    Body: {
      Text: {
        Charset: "UTF-8",
        Data: "A controlled message from the approved application sender.",
      },
    },
  },
}), { abortSignal: AbortSignal.timeout(15000) });

console.log({ providerMessageId: result.MessageId });

Running node send-ses.mjs sends real mail and may incur provider usage. The explicit single attempt makes retry ownership visible in this small example. A timeout still has an ambiguous outcome: SES may have accepted the message before the client stopped waiting. Do not blindly resend.

Track acceptance separately from delivery

The returned MessageId records SES acceptance, not final delivery to the recipient’s inbox. Persist the application send attempt before making the call, then store the provider ID against it.

Configure the appropriate SES event publishing for delivery, bounces, and complaints. If using a configuration set, create it and its event destination first, and explicitly select it for the send or configure the identity’s default. Do not add a nonexistent configuration-set name to a production request.

An event handler must authenticate the actual delivery mechanism, process duplicates safely, and update suppression policy. A bounce should not launch an unlimited resend loop. Before retrying an uncertain send, reconcile with provider events and your application record as far as the available evidence permits.

Diagnose failures by boundary

  • Access denied: check IAM role, identity resource, Region, and any sending conditions.
  • Identity or recipient rejection: check verification and sandbox status in the request’s Region.
  • Throttling or sending disabled: check account limits and status; pause or queue rather than bypassing enforcement.
  • Accepted but absent from the inbox: examine delivery/bounce evidence and recipient filtering, not just the API response.

Where MailKite differs

MailKite offers a separate SDK-first sending contract and inbound webhooks. If you choose that path, use MailKite credentials and domain verification; do not pass AWS credentials to the MailKite SDK. During migration, route each message through only one transport and keep a rollback decision in application configuration.

References