SES
Lightweight, dependency-free fake of AWS SES (Simple Email Service, the classic v1 API) that speaks the real SES AWS Query wire protocol (form-encoded requests, XML responses, API version 2010-12-01), so application code using @aws-sdk/client-ses can run against it with zero cost and zero side effects.
| Key | Value |
|---|---|
| Port | 4570 |
| Protocol | AWS Query (application/x-www-form-urlencoded request, XML response) over HTTP |
| API version | 2010-12-01 |
| Compatible client | @aws-sdk/client-ses (v3) |
| Size | ~80 KB |
| Startup | < 100ms |
| State | In-memory, ephemeral, resettable |
Quick Start
Start the server:
import { SesServer } from "./services/ses/src/server.js";
const server = new SesServer(4570);
await server.start();
// ... use it ...
await server.stop();
Connect with the real AWS SDK client:
import {
SESClient,
VerifyEmailIdentityCommand,
SendEmailCommand,
} from "@aws-sdk/client-ses";
const ses = new SESClient({
region: "us-east-1",
endpoint: "http://127.0.0.1:4570",
credentials: { accessKeyId: "parlel", secretAccessKey: "parlel" },
});
// Verify a sender (auto-verified in the fake)
await ses.send(new VerifyEmailIdentityCommand({ EmailAddress: "sender@example.com" }));
// Send an email
const { MessageId } = await ses.send(
new SendEmailCommand({
Source: "sender@example.com",
Destination: { ToAddresses: ["dest@example.com"] },
Message: {
Subject: { Data: "Hello" },
Body: { Text: { Data: "World" } },
},
}),
);
Implemented operations
All 71 operations exposed by @aws-sdk/client-ses are implemented.
Identities & verification
VerifyEmailAddress(legacy) — auto-verifies an emailVerifyEmailIdentity— auto-verifies an emailVerifyDomainIdentity— returns aVerificationTokenVerifyDomainDkim— returns 3 deterministicDkimTokensDeleteIdentityDeleteVerifiedEmailAddress(legacy)ListIdentities— supportsIdentityType,MaxItems,NextTokenListVerifiedEmailAddresses(legacy)GetIdentityVerificationAttributesGetIdentityDkimAttributesSetIdentityDkimEnabledGetIdentityMailFromDomainAttributesSetIdentityMailFromDomainGetIdentityNotificationAttributesSetIdentityNotificationTopicSetIdentityFeedbackForwardingEnabledSetIdentityHeadersInNotificationsEnabled
Identity policies
PutIdentityPolicyGetIdentityPoliciesListIdentityPoliciesDeleteIdentityPolicy
Sending
SendEmail— enforces verifiedSource, requires recipients + subject/bodySendRawEmail— extractsFrom:for verification whenSourceis omittedSendTemplatedEmail— requires an existing template, validatesTemplateDataJSONSendBulkTemplatedEmail— returns per-destinationStatusSendCustomVerificationEmailSendBounce
Account & statistics
GetSendQuota—Max24HourSend,MaxSendRate,SentLast24HoursGetSendStatisticsGetAccountSendingEnabledUpdateAccountSendingEnabled— pausing blocks allSend*calls
Email templates
CreateTemplateGetTemplateUpdateTemplateDeleteTemplate(idempotent)ListTemplatesTestRenderTemplate—{{placeholder}}substitution, errors on missing data
Custom verification email templates
CreateCustomVerificationEmailTemplateGetCustomVerificationEmailTemplateUpdateCustomVerificationEmailTemplateDeleteCustomVerificationEmailTemplateListCustomVerificationEmailTemplates
Configuration sets
CreateConfigurationSetDescribeConfigurationSet— honorsConfigurationSetAttributeNamesDeleteConfigurationSetListConfigurationSetsPutConfigurationSetDeliveryOptionsUpdateConfigurationSetReputationMetricsEnabledUpdateConfigurationSetSendingEnabled
Configuration set event destinations
CreateConfigurationSetEventDestinationUpdateConfigurationSetEventDestinationDeleteConfigurationSetEventDestination
Configuration set tracking options
CreateConfigurationSetTrackingOptionsUpdateConfigurationSetTrackingOptionsDeleteConfigurationSetTrackingOptions
Receipt rule sets & rules
CreateReceiptRuleSetDeleteReceiptRuleSet— refuses to delete the active setDescribeReceiptRuleSetListReceiptRuleSetsCloneReceiptRuleSetDescribeActiveReceiptRuleSetSetActiveReceiptRuleSetReorderReceiptRuleSetCreateReceiptRule— supportsAfterpositioningUpdateReceiptRuleDeleteReceiptRuleDescribeReceiptRuleSetReceiptRulePosition
Receipt filters
CreateReceiptFilterDeleteReceiptFilterListReceiptFilters
Internal (non-SES) endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /_parlel/health | Health probe — returns { status, service, identities, templates, configurationSets } |
| POST | /_parlel/reset | Clears all in-memory state |
| GET | /_parlel/sent | Returns captured sent messages (for test assertions) |
server.reset() resets all state in-process as well.
Surface coverage
This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.
Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.
| Feature | Supported | Notes |
|---|---|---|
| Full SES v1 API surface (71 operations) | ✅ | Every @aws-sdk/client-ses command |
| AWS Query protocol (form request / XML response) | ✅ | API version 2010-12-01 |
| Identity verification | ✅ | Emails & domains auto-verify; DKIM tokens are deterministic |
| Identity policies, MAIL FROM, notifications | ✅ | Stored in-memory per identity |
| Email / raw / templated / bulk / custom verification sending | ✅ | Sender verification enforced |
Template rendering ({{placeholder}}) | ✅ | Basic mustache-style substitution |
| Configuration sets, event destinations, tracking, delivery options | ✅ | Stored & described |
| Receipt rule sets, rules, filters | ✅ | Full CRUD + ordering + active set |
| Send quota / statistics | ✅ | Quota fixed at 200/day, rate 1/s; counter tracks sends |
| Real email delivery | ⟳ Roadmap | |
| Real DKIM/SPF/DMARC validation | ✓ By design — Intentional for a local, zero-cost test emulator | |
| Actual event/notification publishing to SNS/Firehose/CloudWatch | ✓ By design — Intentional for a local, zero-cost test emulator | |
| Sandbox/production-access enforcement | ⟳ Roadmap | |
| IAM / SigV4 signature verification | ✓ By design — Structurally faithful tokens; cryptographic verification is skipped for local use | |
SESv2 API (@aws-sdk/client-sesv2) | ⟳ Roadmap |
Error codes & shapes
Errors are returned as standard AWS Query XML with a non-2xx status:
<?xml version="1.0"?>
<ErrorResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
<Error>
<Type>Sender</Type>
<Code>MessageRejected</Code>
<Message>Email address is not verified. ...</Message>
</Error>
<RequestId>...</RequestId>
</ErrorResponse>
Type is Sender for 4xx (client fault) and Receiver for 5xx (server fault). Common codes:
| Code | Status | When |
|---|---|---|
InvalidParameterValue | 400 | Bad / missing parameters, invalid email |
MessageRejected | 400 | Sending from an unverified identity |
AccountSendingPausedException | 400 | Account sending disabled |
TemplateDoesNotExistException | 400 | Template not found |
AlreadyExistsException | 400 | Duplicate template / rule / filter / rule set |
ConfigurationSetDoesNotExistException | 400 | Config set not found |
ConfigurationSetAlreadyExistsException | 400 | Duplicate config set |
EventDestinationAlreadyExistsException | 400 | Duplicate event destination |
EventDestinationDoesNotExistException | 400 | Event destination not found |
TrackingOptionsAlreadyExistsException | 400 | Tracking options already set |
TrackingOptionsDoesNotExistException | 400 | Tracking options not set |
CustomVerificationEmailTemplateAlreadyExistsException | 400 | Duplicate custom template |
CustomVerificationEmailTemplateDoesNotExistException | 400 | Custom template not found |
RuleSetDoesNotExistException | 400 | Receipt rule set not found |
RuleDoesNotExistException | 400 | Receipt rule not found |
CannotDeleteException | 400 | Deleting the active receipt rule set |
MissingRenderingAttributeException | 400 | TestRenderTemplate data missing a placeholder |
InvalidAction | 400 | Unknown Action |
InternalError | 500 | Unexpected server fault |
Configuration — test.env
Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.
AWS_ACCESS_KEY_ID=parlel
AWS_SECRET_ACCESS_KEY=parlel
AWS_REGION=us-east-1
AWS_ENDPOINT_URL_SES=http://parlel-bridge:4570
AWS_ENDPOINT_URL=http://parlel-bridge:4570
<!-- parlel:testenv:end -->