SES
Protocol: Query (XML) with Action= parameter
Endpoint: POST http://localhost:4566/
Floci exposes the classic Amazon SES Query API used by aws ses ... commands and SDKs targeting SES v1.
Supported Actions
| Action | Description |
|---|---|
VerifyEmailIdentity |
Mark an email address as verified |
VerifyEmailAddress |
Legacy alias for email verification |
VerifyDomainIdentity |
Mark a domain as verified and return a verification token |
DeleteIdentity |
Delete an email or domain identity |
ListIdentities |
List verified identities |
GetIdentityVerificationAttributes |
Get verification status for one or more identities |
SendEmail |
Send a structured email with text or HTML body |
SendRawEmail |
Send a raw MIME payload |
SendTemplatedEmail |
Send an email by resolving a stored template |
SendBulkTemplatedEmail |
Send a templated email to multiple destinations |
CreateTemplate |
Create an email template with subject / text / html parts |
GetTemplate |
Read a stored template |
UpdateTemplate |
Replace the content of a stored template |
DeleteTemplate |
Remove a stored template |
ListTemplates |
List stored templates |
TestRenderTemplate |
Render a stored template against supplied data, returning the MIME message |
CreateCustomVerificationEmailTemplate |
Create a custom verification email template |
GetCustomVerificationEmailTemplate |
Return a custom verification email template |
ListCustomVerificationEmailTemplates |
List custom verification email templates (no content) |
UpdateCustomVerificationEmailTemplate |
Replace a custom verification email template |
DeleteCustomVerificationEmailTemplate |
Delete a custom verification email template |
SendCustomVerificationEmail |
Send a custom verification email and register the recipient as a pending identity |
GetSendQuota |
Return local send quota counters |
GetSendStatistics |
Return aggregate delivery stats for sent messages |
GetAccountSendingEnabled |
Report whether sending is enabled |
UpdateAccountSendingEnabled |
Enable or disable account-wide sending |
ListVerifiedEmailAddresses |
List verified email identities |
DeleteVerifiedEmailAddress |
Delete a verified email identity |
SetIdentityNotificationTopic |
Set the SNS topic for an identity's bounce/complaint/delivery notifications |
GetIdentityNotificationAttributes |
Read stored notification topic settings |
SetIdentityFeedbackForwardingEnabled |
Toggle feedback forwarding for an identity |
SetIdentityHeadersInNotificationsEnabled |
Toggle headers-in-notifications per notification type |
SetIdentityMailFromDomain |
Set or clear the MAIL FROM domain for an identity |
GetIdentityMailFromDomainAttributes |
Read MAIL FROM domain settings |
GetIdentityDkimAttributes |
Return DKIM status for identities (an email inherits its domain's DKIM) |
SetIdentityDkimEnabled |
Enable or disable DKIM signing for an identity |
VerifyDomainDkim |
Return a domain's (stable) DKIM CNAME tokens |
PutIdentityPolicy |
Create or replace a sending-authorization policy on an identity |
GetIdentityPolicies |
Return the requested policies for an identity |
ListIdentityPolicies |
List an identity's policy names |
DeleteIdentityPolicy |
Delete a policy from an identity |
CreateConfigurationSet |
Create a configuration set |
DescribeConfigurationSet |
Read a configuration set |
ListConfigurationSets |
List configuration sets |
DeleteConfigurationSet |
Delete a configuration set |
CreateConfigurationSetEventDestination |
Attach an event destination to a configuration set |
UpdateConfigurationSetEventDestination |
Update an existing event destination on a configuration set |
DeleteConfigurationSetEventDestination |
Remove an event destination from a configuration set |
UpdateConfigurationSetSendingEnabled |
Enable or disable email sending through a configuration set |
CreateConfigurationSetTrackingOptions |
Set the custom open/click tracking redirect domain |
UpdateConfigurationSetTrackingOptions |
Change the custom tracking redirect domain |
DeleteConfigurationSetTrackingOptions |
Remove the custom tracking redirect domain |
UpdateConfigurationSetReputationMetricsEnabled |
Enable or disable reputation metrics for a configuration set |
PutConfigurationSetDeliveryOptions |
Set the TLS policy (delivery options) for a configuration set |
CreateReceiptRuleSet |
Create a receipt rule set (stored inertly) |
DescribeReceiptRuleSet |
Read a receipt rule set (Rules always empty) |
ListReceiptRuleSets |
List receipt rule sets |
DeleteReceiptRuleSet |
Delete a receipt rule set (idempotent) |
SetActiveReceiptRuleSet |
Mark a rule set active, or clear the active one |
DescribeActiveReceiptRuleSet |
Read the active receipt rule set |
Configuration
| Variable | Default | Description |
|---|---|---|
FLOCI_SERVICES_SES_ENABLED |
true |
Enable or disable the SES service |
FLOCI_SERVICES_SES_SMTP_HOST |
(unset) | SMTP server host for email relay (empty = store only) |
FLOCI_SERVICES_SES_SMTP_PORT |
25 |
SMTP server port |
FLOCI_SERVICES_SES_SMTP_USER |
(unset) | SMTP authentication username |
FLOCI_SERVICES_SES_SMTP_PASS |
(unset) | SMTP authentication password |
FLOCI_SERVICES_SES_SMTP_STARTTLS |
DISABLED |
STARTTLS mode: DISABLED, OPTIONAL, or REQUIRED |
SMTP Relay
When smtp-host is configured, SendEmail and SendRawEmail forward
emails to the specified SMTP server in addition to storing them in the
local inspection endpoint. This enables integration testing with tools
like Mailpit or any standard SMTP server.
# docker-compose.yml
services:
floci:
image: floci/floci:latest
ports: ["4566:4566"]
environment:
FLOCI_SERVICES_SES_SMTP_HOST: mailpit
FLOCI_SERVICES_SES_SMTP_PORT: 1025
networks: [floci]
mailpit:
image: axllent/mailpit
ports:
- "8025:8025" # Web UI
- "1025:1025" # SMTP
networks: [floci]
networks:
floci:
- Emails are always stored locally regardless of relay — the
/_aws/sesinspection endpoint works with or without SMTP. - Relay failures are logged but do not affect the API response.
- Raw MIME messages are parsed with Apache Mime4j to extract common fields (From, To, Cc, Subject, text/plain and text/html parts) and relayed as a reconstructed message. Arbitrary headers, attachments, and complex multipart structures are not preserved in the relay.
Local Inspection Endpoint
For test assertions and debugging, Floci exposes a LocalStack-compatible mailbox endpoint:
GET /_aws/seslists captured messagesGET /_aws/ses?id=<message-id>returns a specific captured messageDELETE /_aws/sesclears the captured mailbox
Messages are stored locally by Floci and can be persisted when SES storage is backed by persistent or hybrid storage.
Examples
export AWS_ENDPOINT_URL=http://localhost:4566
# Verify sender and recipient identities
aws ses verify-email-identity \
--email-address sender@example.com \
--endpoint-url $AWS_ENDPOINT_URL
aws ses verify-email-identity \
--email-address recipient@example.com \
--endpoint-url $AWS_ENDPOINT_URL
# Verify a domain
aws ses verify-domain-identity \
--domain example.com \
--endpoint-url $AWS_ENDPOINT_URL
# List all identities
aws ses list-identities \
--endpoint-url $AWS_ENDPOINT_URL
# Send a plain-text email
aws ses send-email \
--from sender@example.com \
--destination ToAddresses=recipient@example.com \
--message "Subject={Data=Hello},Body={Text={Data=Sent from Floci SES}}" \
--endpoint-url $AWS_ENDPOINT_URL
# Send a raw MIME email
aws ses send-raw-email \
--raw-message Data="$(printf 'Subject: Raw test\r\n\r\nHello from raw SES')" \
--source sender@example.com \
--destinations recipient@example.com \
--endpoint-url $AWS_ENDPOINT_URL
# Inspect locally captured messages
curl $AWS_ENDPOINT_URL/_aws/ses
Current Behavior
- Identity verification succeeds immediately; no real DNS or inbox verification flow is required.
SendEmailstores the text body or the HTML body as the captured message body.SetIdentityNotificationTopicpublishes to the configured topic on a Bounce/Complaint/Delivery event (triggered via the mailbox simulator addresses or the suppression list), independent of any configuration set. The payload uses the legacy format (notificationType, nomail.tags, headers only whenSetIdentityHeadersInNotificationsEnabledis on).- Identity (sending authorization) policies are stored and returned as metadata: the policy document, the per-identity limit of 20, and the create/update/delete error shapes match AWS, but Floci does not evaluate policy authorization (Principal-account existence, Resource-ARN match) or gate sending on it.
- Receipt rule sets are stored inertly: Floci has no inbound-mail endpoint, so a rule set never holds any receipt rules and routes no mail.
CreateReceiptRuleSet/DescribeReceiptRuleSet(Rules always empty) /ListReceiptRuleSets/DeleteReceiptRuleSet(idempotent) andSetActiveReceiptRuleSet/DescribeActiveReceiptRuleSetround-trip so tools like Terraform (aws_ses_receipt_rule_set,aws_ses_active_receipt_rule_set) can declare a rule set during bootstrap. Individual receipt rules and receipt filters are not implemented. - Custom verification email templates are stored and returned;
Create/Updaterequire theFromEmailAddressto be a verified identity (or a verified domain) and reject an invalid redirection URL, matching AWS.SendCustomVerificationEmailrenders the template into the/_aws/sesinspection mailbox (and the SMTP relay, when configured) and registers the recipient as a pending-verification identity, matching AWS. The template body has no placeholder that AWS substitutes, so it is passed through verbatim with the same fixed disclaimer AWS always appends; the unique verification link AWS appends is not reproduced because Floci has no verification-click flow. TheSuccessRedirectionURL/FailureRedirectionURLare stored and returned by Get/List but are the post-click redirect targets, so they are not used at send time. - For the REST JSON API see SES v2 below.
SES v2 (REST JSON)
Protocol: REST JSON
Endpoint: http://localhost:4566/v2/email/...
Alongside the classic Query API, Floci implements a subset of the SES v2 REST JSON API used by aws sesv2 ... commands and SDK v2 clients that target the modern SES surface.
Supported Operations
| Method | Path | Action |
|---|---|---|
POST |
/v2/email/identities |
CreateEmailIdentity |
GET |
/v2/email/identities |
ListEmailIdentities |
GET |
/v2/email/identities/{emailIdentity} |
GetEmailIdentity |
DELETE |
/v2/email/identities/{emailIdentity} |
DeleteEmailIdentity |
PUT |
/v2/email/identities/{emailIdentity}/dkim |
PutEmailIdentityDkimAttributes |
PUT |
/v2/email/identities/{emailIdentity}/dkim/signing |
PutEmailIdentityDkimSigningAttributes (Easy DKIM / BYODKIM) |
PUT |
/v2/email/identities/{emailIdentity}/feedback |
PutEmailIdentityFeedbackAttributes |
PUT |
/v2/email/identities/{emailIdentity}/mail-from |
PutEmailIdentityMailFromAttributes |
PUT |
/v2/email/identities/{emailIdentity}/configuration-set |
PutEmailIdentityConfigurationSetAttributes |
POST |
/v2/email/identities/{emailIdentity}/policies/{policyName} |
CreateEmailIdentityPolicy |
GET |
/v2/email/identities/{emailIdentity}/policies |
GetEmailIdentityPolicies |
PUT |
/v2/email/identities/{emailIdentity}/policies/{policyName} |
UpdateEmailIdentityPolicy |
DELETE |
/v2/email/identities/{emailIdentity}/policies/{policyName} |
DeleteEmailIdentityPolicy |
POST |
/v2/email/outbound-emails |
SendEmail (simple / raw / templated) |
POST |
/v2/email/outbound-bulk-emails |
SendBulkEmail (templated, multiple destinations) |
GET |
/v2/email/account |
GetAccount |
PUT |
/v2/email/account/sending |
PutAccountSendingAttributes |
PUT |
/v2/email/account/suppression |
PutAccountSuppressionAttributes |
PUT |
/v2/email/account/vdm |
PutAccountVdmAttributes |
POST |
/v2/email/templates |
CreateEmailTemplate |
GET |
/v2/email/templates |
ListEmailTemplates |
GET |
/v2/email/templates/{templateName} |
GetEmailTemplate |
PUT |
/v2/email/templates/{templateName} |
UpdateEmailTemplate |
DELETE |
/v2/email/templates/{templateName} |
DeleteEmailTemplate |
POST |
/v2/email/templates/{templateName}/render |
TestRenderEmailTemplate |
POST |
/v2/email/custom-verification-email-templates |
CreateCustomVerificationEmailTemplate |
GET |
/v2/email/custom-verification-email-templates |
ListCustomVerificationEmailTemplates |
GET |
/v2/email/custom-verification-email-templates/{templateName} |
GetCustomVerificationEmailTemplate |
PUT |
/v2/email/custom-verification-email-templates/{templateName} |
UpdateCustomVerificationEmailTemplate |
DELETE |
/v2/email/custom-verification-email-templates/{templateName} |
DeleteCustomVerificationEmailTemplate |
POST |
/v2/email/outbound-custom-verification-emails |
SendCustomVerificationEmail |
POST |
/v2/email/configuration-sets |
CreateConfigurationSet |
GET |
/v2/email/configuration-sets |
ListConfigurationSets |
GET |
/v2/email/configuration-sets/{name} |
GetConfigurationSet |
DELETE |
/v2/email/configuration-sets/{name} |
DeleteConfigurationSet |
POST |
/v2/email/configuration-sets/{name}/event-destinations |
CreateConfigurationSetEventDestination |
GET |
/v2/email/configuration-sets/{name}/event-destinations |
GetConfigurationSetEventDestinations |
PUT |
/v2/email/configuration-sets/{name}/event-destinations/{eventDestinationName} |
UpdateConfigurationSetEventDestination |
DELETE |
/v2/email/configuration-sets/{name}/event-destinations/{eventDestinationName} |
DeleteConfigurationSetEventDestination |
PUT |
/v2/email/configuration-sets/{name}/suppression-options |
PutConfigurationSetSuppressionOptions |
PUT |
/v2/email/configuration-sets/{name}/sending |
PutConfigurationSetSendingOptions |
PUT |
/v2/email/configuration-sets/{name}/reputation-options |
PutConfigurationSetReputationOptions |
PUT |
/v2/email/configuration-sets/{name}/tracking-options |
PutConfigurationSetTrackingOptions |
PUT |
/v2/email/configuration-sets/{name}/delivery-options |
PutConfigurationSetDeliveryOptions |
PUT |
/v2/email/configuration-sets/{name}/archiving-options |
PutConfigurationSetArchivingOptions |
PUT |
/v2/email/configuration-sets/{name}/vdm-options |
PutConfigurationSetVdmOptions |
POST |
/v2/email/dedicated-ip-pools |
CreateDedicatedIpPool |
GET |
/v2/email/dedicated-ip-pools |
ListDedicatedIpPools |
GET |
/v2/email/dedicated-ip-pools/{PoolName} |
GetDedicatedIpPool |
DELETE |
/v2/email/dedicated-ip-pools/{PoolName} |
DeleteDedicatedIpPool |
POST |
/v2/email/contact-lists |
CreateContactList |
GET |
/v2/email/contact-lists |
ListContactLists |
GET |
/v2/email/contact-lists/{ContactListName} |
GetContactList |
PUT |
/v2/email/contact-lists/{ContactListName} |
UpdateContactList |
DELETE |
/v2/email/contact-lists/{ContactListName} |
DeleteContactList |
POST |
/v2/email/contact-lists/{ContactListName}/contacts |
CreateContact |
POST |
/v2/email/contact-lists/{ContactListName}/contacts/list |
ListContacts |
GET |
/v2/email/contact-lists/{ContactListName}/contacts/{EmailAddress} |
GetContact |
PUT |
/v2/email/contact-lists/{ContactListName}/contacts/{EmailAddress} |
UpdateContact |
DELETE |
/v2/email/contact-lists/{ContactListName}/contacts/{EmailAddress} |
DeleteContact |
PUT |
/v2/email/suppression/addresses |
PutSuppressedDestination |
GET |
/v2/email/suppression/addresses/{EmailAddress} |
GetSuppressedDestination |
DELETE |
/v2/email/suppression/addresses/{EmailAddress} |
DeleteSuppressedDestination |
GET |
/v2/email/suppression/addresses |
ListSuppressedDestinations (optional Reason query filter) |
POST |
/v2/email/tags |
TagResource |
DELETE |
/v2/email/tags?ResourceArn=...&TagKeys=... |
UntagResource |
GET |
/v2/email/tags?ResourceArn=... |
ListTagsForResource |
Configuration set event destinations are stored as configuration. The target is not validated for existence; missing targets cause Floci to log a warning and skip that destination. Each event destination must specify exactly one destination type and at least one matching event type. A CloudWatch destination requires a non-empty dimension configuration list, and a Pinpoint destination requires an application ARN.
Floci publishes SES events to SnsDestination, KinesisFirehoseDestination, EventBridgeDestination, and CloudWatchDestination. PinpointDestination logs a warning and skips. The published payload follows the AWS SES SNS notification format with an outer eventType plus mail and event-type-specific blocks. Events fire whenever a configuration set has at least one event destination matching the event type — disable per-destination via EventDestination.Enabled=false, or remove the destination entirely.
Floci recognises the AWS mailbox simulator addresses for deterministic event-type emission:
| Recipient address | Events emitted (in addition to Send) |
|---|---|
success@simulator.amazonses.com |
Delivery |
bounce@simulator.amazonses.com |
Bounce |
complaint@simulator.amazonses.com |
Complaint |
suppressionlist@simulator.amazonses.com |
Reject |
A successful send without a simulator-address recipient emits only the Send event.
Account-level VDM (Virtual Deliverability Manager) attributes are stored per region. PutAccountVdmAttributes sets VdmEnabled (opt-in, defaults DISABLED) plus the optional DashboardAttributes.EngagementMetrics and GuardianAttributes.OptimizedSharedDelivery. GetAccount omits VdmAttributes until VDM has been configured for the region, then returns VdmEnabled, adding the DashboardAttributes/GuardianAttributes sub-objects only while VdmEnabled is ENABLED. Floci stores the settings but does not run VDM analytics.
Suppression list entries are stored per region with Reason ∈ {BOUNCE, COMPLAINT}. At send time, a recipient is suppressed when it appears on the suppression list AND its stored Reason is contained in the effective SuppressedReasons for the send. The effective list is the configuration set's SuppressionOptions.SuppressedReasons (set via PutConfigurationSetSuppressionOptions) when present — an empty list is preserved as an explicit "no suppression filtering for this configuration set" — otherwise it falls back to the account-level AccountSuppressionAttributes.SuppressedReasons (set via PutAccountSuppressionAttributes, default [BOUNCE, COMPLAINT]). Following the AWS V2 contract, there is no dedicated GetConfigurationSetSuppressionOptions action; once set, the block is read back through GetConfigurationSet's response (the field is omitted when the configuration set has no override).
Suppressed recipients are filtered out of the SMTP relay step (non-suppressed recipients on the same send still reach the relay normally), and the configuration set's event destinations receive a synthetic Bounce or Complaint event alongside the always-emitted Send event. The SendEmail API response (200 + MessageId), the stored SentEmail visible at GET /_aws/ses, and the published event's mail.destination all retain the original recipient list — matching the AWS contract that the message is "accepted, just not sent" for suppressed addresses.
SendEmail honors ListManagementOptions (ContactListName, optional TopicName). When present, each recipient is matched against the named contact list and suppressed as a Bounce when opted out — reusing the same relay-exclusion and Bounce-event path as suppression-list filtering, matching AWS ("SES will issue a bounce event for a message that is sent to an unsubscribed contact"). A contact is opted out when UnsubscribeAll is set; with a TopicName, an explicit OPT_OUT preference for that topic (or, absent an explicit preference, the topic's DefaultSubscriptionStatus being OPT_OUT) suppresses; without a TopicName, only UnsubscribeAll contacts are suppressed. A recipient that is not yet a contact is created on the list automatically (as on AWS) and then evaluated. Referencing a contact list that does not exist fails the send with NotFoundException. The topic-default fallback at send time is an intentional deviation — it is not documented by AWS and mirrors the effective-status model AWS uses for ListContacts.
For a single-recipient list-managed send, Floci injects a functional unsubscribe link (matching AWS, which only does this for one recipient): the {{amazonSESUnsubscribeUrl}} body placeholder is replaced (up to twice) and a List-Unsubscribe header plus List-Unsubscribe-Post: List-Unsubscribe=One-Click are added (applied to the relayed message and shown under Headers at GET /_aws/ses; a caller-supplied List-Unsubscribe is overridden, matching AWS). The {{amazonSESUnsubscribeUrl}} placeholder is also preserved through template rendering so a templated body can carry it. On a send without ListManagementOptions, the placeholder is left in the body verbatim (neither replaced nor stripped) — this specific behavior is Floci's choice and is not verified against real SES. Unlike AWS's opaque hosted URL, the link points at Floci's own /_aws/ses/unsubscribe?region=…&contactList=…&address=…&topic=… endpoint. GET (a browser click) only renders a confirmation page and changes nothing — matching AWS's landing-page behavior and avoiding the RFC 8058 hazard where a client or bot that prefetches the link would silently unsubscribe the contact — while POST (the one-click request the confirmation form submits) applies the opt-out (a topic → OPT_OUT for that topic; no topic → UnsubscribeAll), auto-creating the contact if needed. Two deviations from AWS here: the link is carried as readable query parameters rather than an opaque token (so, like the rest of Floci, the endpoint is unauthenticated and only safe on a trusted dev/test network — a POST can opt out any contact), and only the one-click URL entry is emitted — its scheme follows Floci's base URL (http unless TLS is enabled) — whereas AWS also includes a mailto: entry, which Floci can't service since it has no inbound mail endpoint. Raw (MIME) sends do not get link injection yet.
Tag operations support these ARN forms: arn:aws:ses:<region>:<account>:configuration-set/<name>, arn:aws:ses:<region>:<account>:template/<name>, and arn:aws:ses:<region>:<account>:identity/<email-or-domain>. Tags supplied to CreateConfigurationSet, CreateEmailTemplate, and CreateEmailIdentity are reachable through ListTagsForResource; UpdateEmailTemplate does not modify tags. Other resource types return NotFoundException.
Identity, identity-policy, template, custom-verification-template, configuration-set, and sent-message state is shared between the v1 Query API and the v2 REST JSON API, so a template created with CreateTemplate resolves through SendEmail on v2 (and vice versa), a policy written with PutIdentityPolicy (v1) is returned by GetEmailIdentityPolicies (v2), a custom verification email template created with CreateCustomVerificationEmailTemplate (v1) is returned by GetCustomVerificationEmailTemplate (v2), a configuration set created with CreateConfigurationSet is visible to both DescribeConfigurationSet (v1) and GetConfigurationSet (v2), and every send appears in the same GET /_aws/ses inspection mailbox.
DKIM follows AWS's domain-centric model. A domain identity carries DKIM tokens (generated at verification, stable across VerifyDomainDkim calls); its DkimVerificationStatus tracks DNS record detection — it transitions to Success when the expected <token>._domainkey.<domain> CNAMEs are present in the Route53 emulation, not when DKIM is enabled. An email identity has no DKIM of its own: its DkimAttributes (SigningEnabled, Status, Tokens) are inherited from its parent domain identity when one is registered. The parent is the exact domain after the @ (verified against AWS): a verified example.com covers user@example.com but not user@mail.example.com unless mail.example.com is itself a registered identity. SetIdentityDkimEnabled / PutEmailIdentityDkimAttributes only toggle the signing flag (they no longer force the verification status); PutEmailIdentityDkimSigningAttributes sets the signing origin (AWS_SES Easy DKIM — regenerating tokens when the key length changes — or EXTERNAL BYODKIM).