SES emulator for tests

Run AWS SES locally for integration tests with fakecloud. 110 SES operations, v2 send + templates + DKIM, real receipt rule execution for inbound email. Free, no account required.

Need a SES emulator for integration tests? Use fakecloud. Not a mock library — a real server that speaks the SES wire protocol.

curl -fsSL https://raw.githubusercontent.com/faiscadev/fakecloud/main/install.sh | bash
fakecloud

Point your AWS SDK at http://localhost:4566.

Why fakecloud for SES

Send an email

Python (boto3)

import boto3
ses = boto3.client('sesv2',
    endpoint_url='http://localhost:4566',
    aws_access_key_id='test',
    aws_secret_access_key='test',
    region_name='us-east-1')

ses.send_email(
    FromEmailAddress='sender@example.com',
    Destination={'ToAddresses': ['alice@example.com']},
    Content={'Simple': {
        'Subject': {'Data': 'Hello'},
        'Body': {'Text': {'Data': 'World'}}
    }})

Node.js (AWS SDK v3)

import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
const ses = new SESv2Client({});

await ses.send(new SendEmailCommand({
  FromEmailAddress: 'sender@example.com',
  Destination: { ToAddresses: ['alice@example.com'] },
  Content: { Simple: {
    Subject: { Data: 'Hello' },
    Body: { Text: { Data: 'World' } },
  }},
}));

Assert what was sent

import { FakeCloud } from 'fakecloud';
const fc = new FakeCloud();

const { emails } = await fc.ses.getEmails();
expect(emails).toHaveLength(1);
expect(emails[0].destination.toAddresses).toContain('alice@example.com');
expect(emails[0].content.simple.subject.data).toBe('Hello');

await fc.reset();

SDKs for TypeScript, Python, Go, PHP, Java, Rust.

Templates

ses.create_email_template(
    TemplateName='welcome',
    TemplateContent={
        'Subject': 'Hi {{name}}',
        'Text': 'Welcome, {{name}}!',
    })

ses.send_email(
    FromEmailAddress='sender@example.com',
    Destination={'ToAddresses': ['alice@example.com']},
    Content={'Template': {
        'TemplateName': 'welcome',
        'TemplateData': '{"name":"Alice"}',
    }})

Inbound email with receipt rules

aws --endpoint-url http://localhost:4566 ses create-receipt-rule-set --rule-set-name default
aws --endpoint-url http://localhost:4566 ses set-active-receipt-rule-set --rule-set-name default

aws --endpoint-url http://localhost:4566 ses create-receipt-rule \
  --rule-set-name default \
  --rule '{
    "Name": "save-to-s3",
    "Enabled": true,
    "Recipients": ["support@example.com"],
    "Actions": [{
      "S3Action": {"BucketName": "inbound-mail"}
    }, {
      "LambdaAction": {"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:on-inbound"}
    }]
  }'

Simulate an inbound email:

curl -X POST http://localhost:4566/_fakecloud/ses/simulate-inbound \
  -H 'Content-Type: application/json' \
  -d '{"to": "support@example.com", "from": "buyer@example.com", "subject": "Question", "body": "Help?"}'

The receipt rule fires: the email lands in S3, the Lambda runs with the SES event shape. End-to-end.

Configuration sets + event destinations

ses.create_configuration_set(ConfigurationSetName='default')
ses.create_configuration_set_event_destination(
    ConfigurationSetName='default',
    EventDestinationName='sns-events',
    EventDestination={
        'Enabled': True,
        'MatchingEventTypes': ['SEND', 'DELIVERY', 'BOUNCE', 'COMPLAINT'],
        'SnsDestination': {'TopicArn': 'arn:aws:sns:us-east-1:000000000000:ses-events'},
    })

Sending through this configuration set publishes send/delivery/bounce events to SNS for real.

Bounce / complaint simulation

Use AWS's mailbox simulator addresses for test-path bounces:

fakecloud emits the corresponding events via configured destinations.

How it differs from alternatives

ToolMulti-languageInbound receipt rules executeDKIMTemplates
fakecloudAnyYes (real actions fire)YesYes
LocalStack Community (post-paywall)Any (auth required)No (stored but never executed)PaidPaid
Moto (mock_ses)Python onlyStubbedPartialPartial
maildev / MailHogAny (SMTP, not AWS SES API)N/AN/AN/A

Real inbound receipt rule execution is the big differentiator — critical if your app does SES-inbound -> S3/Lambda pipelines.