Skip to content

feat(ses): implement PutEmailIdentityConfigurationSetAttributes for SES v2#1698

Open
okinaka wants to merge 3 commits into
floci-io:mainfrom
okinaka:feat/ses-v2-put-email-identity-config-set
Open

feat(ses): implement PutEmailIdentityConfigurationSetAttributes for SES v2#1698
okinaka wants to merge 3 commits into
floci-io:mainfrom
okinaka:feat/ses-v2-put-email-identity-config-set

Conversation

@okinaka

@okinaka okinaka commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the SES v2 PutEmailIdentityConfigurationSetAttributes API (PUT /v2/email/identities/{EmailIdentity}/configuration-set) and wires an email identity's default configuration set into the send path so its event destinations, suppression and tracking apply automatically.

  • New endpoint associates / clears an identity's default ConfigurationSetName; GetEmailIdentity surfaces it.
  • SendEmail / SendRawEmail with no explicit ConfigurationSetName resolve to the sender identity's default (matched by the exact address first, then the domain). For Raw content with no FromEmailAddress, the sender — and therefore the configuration set — is resolved from the MIME From header.
  • Unknown identities return NotFoundException; an unknown configuration set returns NotFoundException.

Type of change

  • Bug fix (fix:)
  • New feature (feat:)
  • Breaking change (feat!: or fix!:)
  • Docs / chore

AWS Compatibility

Behaviour verified against real AWS (SES v2, boto3 / raw signed requests, us-east-1):

  • Clear vs store. An omitted ConfigurationSetName clears the association (200). An explicit empty string is accepted (200) and — on AWS — stored verbatim; Floci collapses "" into a clear, a deliberate no-op simplification (no SDK/CLI client sends "").
  • Request body. Only a truly empty body is treated as "no body" (clear). A whitespace-only or otherwise unparseable body returns SerializationException (400) and does not clear, matching AWS.
  • Default config-set routing. A send without an explicit configuration set publishes through the identity's default (verified via a config-set with an SNS event destination). This holds for Simple, Templated, and Raw content, including Raw sends that omit FromEmailAddress (sender taken from the MIME From).

Checklist

  • ./mvnw test passes locally
  • New or updated integration test added
  • Commit messages follow Conventional Commits

Copilot AI review requested due to automatic review settings July 2, 2026 09:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds SES v2 support for PutEmailIdentityConfigurationSetAttributes and propagates an email identity’s default configuration set into the send pipeline so configuration-set features (event destinations, suppression, tracking) apply automatically when a send omits ConfigurationSetName. This aligns Floci’s SES v2 surface more closely with AWS SES behavior and includes both RestAssured integration coverage and an AWS SDK v2 compatibility test.

Changes:

  • Implement PUT /v2/email/identities/{EmailIdentity}/configuration-set and surface ConfigurationSetName via GetEmailIdentity.
  • Resolve an effective configuration set during SendEmail / SendRawEmail when ConfigurationSetName is omitted (including Raw sends where sender is derived from MIME From).
  • Add Quarkus integration tests plus an SDK-based compatibility test, and update SES service documentation.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/test/java/io/github/hectorvent/floci/services/ses/SesEmailIdentityConfigurationSetV2IntegrationTest.java New integration coverage for identity default configuration set association + send routing (including Raw/MIME sender resolution).
src/main/java/io/github/hectorvent/floci/services/ses/SesService.java Applies identity-default configuration set resolution in send paths and adds service method to associate/clear the default configuration set.
src/main/java/io/github/hectorvent/floci/services/ses/SesController.java Adds the SES v2 REST endpoint for configuration-set association and includes ConfigurationSetName in identity responses.
src/main/java/io/github/hectorvent/floci/services/ses/model/Identity.java Persists the identity’s ConfigurationSetName in the SES Identity model.
docs/services/ses.md Documents the new SES v2 endpoint.
compatibility-tests/sdk-test-java/src/test/java/com/floci/test/SesEmailIdentityConfigurationSetTest.java Adds AWS SDK v2 compatibility test for the new API behavior (associate/read/clear/unknown identity).

Comment thread src/main/java/io/github/hectorvent/floci/services/ses/SesService.java Outdated
Comment thread src/main/java/io/github/hectorvent/floci/services/ses/SesController.java Outdated
@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown

Greptile Summary

Implements PutEmailIdentityConfigurationSetAttributes for SES v2, allowing a default configuration set to be associated with an email identity. The default is resolved automatically in sendEmail and sendRawEmail when no explicit configuration set is supplied by the caller (email address identity takes precedence over domain, stale associations are silently skipped).

  • New PUT /v2/email/identities/{emailIdentity}/configuration-set endpoint validates both the identity and the named config set, with empty/omitted body treated as a clear operation.
  • sendRawEmail performs a two-phase resolution when FromEmailAddress is omitted, re-resolving the effective config set from the parsed MIME From header so raw sends without an explicit sender still route through the identity's default.
  • GetEmailIdentity surfaces the stored ConfigurationSetName; nine integration tests and an SDK compatibility test cover associate, clear, event routing, and error paths.

Confidence Score: 5/5

This PR is safe to merge. The new endpoint, identity-default config-set resolution, and two-phase raw-email sender resolution are all correctly implemented and well-covered by integration and SDK compatibility tests.

The core send paths (Simple, Raw, and the edge case of Raw without FromEmailAddress) all correctly resolve the effective config set, validate it, and pass it through suppression and event publishing. The stale-association guard in existingDefaultConfigSet prevents a deleted config set from blocking an otherwise valid send. The body-parsing logic faithfully matches the AWS wire behavior (empty body clears, whitespace-only body returns SerializationException). No incorrect data, wrong-scoping, or broken contract issues were found.

No files require special attention.

Important Files Changed

Filename Overview
src/main/java/io/github/hectorvent/floci/services/ses/SesController.java Adds PUT /v2/email/identities/{emailIdentity}/configuration-set endpoint with correct body parsing, SerializationException on malformed input, and surfaces ConfigurationSetName in GetEmailIdentity responses
src/main/java/io/github/hectorvent/floci/services/ses/SesService.java Adds setEmailIdentityConfigurationSet, resolveDefaultConfigurationSet, and existingDefaultConfigSet; wires identity-default config set into sendEmail and sendRawEmail (including the two-phase resolution for raw sends with no FromEmailAddress)
src/main/java/io/github/hectorvent/floci/services/ses/model/Identity.java Adds configurationSetName field with Jackson binding and getter/setter; straightforward model extension
src/test/java/io/github/hectorvent/floci/services/ses/SesEmailIdentityConfigurationSetV2IntegrationTest.java Nine ordered integration tests covering associate/clear, event routing for Simple and Raw content, missing-from-header raw sends, whitespace-body rejection, unknown identity, and unknown config set
compatibility-tests/sdk-test-java/src/test/java/com/floci/test/SesEmailIdentityConfigurationSetTest.java SDK compatibility test (AWS Java SDK v2) covering associate, clear, and NotFoundException for unknown identity; cleanup blocks now log on stderr per AGENTS.md
docs/services/ses.md Single-line doc addition registering the new PUT configuration-set endpoint in the SES v2 table

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant SesController
    participant SesService
    participant IdentityStore
    participant ConfigSetStore

    Client->>SesController: "PUT /v2/email/identities/{id}/configuration-set"
    SesController->>SesController: Parse body → configurationSetName
    SesController->>SesService: setEmailIdentityConfigurationSet(identity, csName, region)
    SesService->>IdentityStore: get(identityKey)
    alt Identity not found
        SesService-->>SesController: NotFoundException (404)
    else Identity found
        alt csName non-blank
            SesService->>ConfigSetStore: getConfigurationSet(csName, region)
            alt Config set not found
                SesService-->>SesController: NotFoundException (404)
            end
        end
        SesService->>IdentityStore: put(identity with csName or null)
        SesController-->>Client: "200 {}"
    end

    Client->>SesController: POST /v2/email/outbound-emails (no ConfigurationSetName)
    SesController->>SesService: sendEmail(source, ..., null, region)
    SesService->>SesService: resolveDefaultConfigurationSet(null, source, region)
    SesService->>IdentityStore: get(identityKey for email)
    alt Email identity has default CS
        SesService-->>SesService: "effectiveConfigSet = identity.configurationSetName"
    else Fall through to domain identity
        SesService->>IdentityStore: get(identityKey for domain)
        alt Domain identity has default CS
            SesService-->>SesService: "effectiveConfigSet = domain.configurationSetName"
        else No default
            SesService-->>SesService: "effectiveConfigSet = null"
        end
    end
    SesService->>SesService: validateConfigurationSet(effectiveConfigSet)
    SesService->>SesService: publishSendEvents(effectiveConfigSet, ...)
    SesController-->>Client: "200 {MessageId}"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant SesController
    participant SesService
    participant IdentityStore
    participant ConfigSetStore

    Client->>SesController: "PUT /v2/email/identities/{id}/configuration-set"
    SesController->>SesController: Parse body → configurationSetName
    SesController->>SesService: setEmailIdentityConfigurationSet(identity, csName, region)
    SesService->>IdentityStore: get(identityKey)
    alt Identity not found
        SesService-->>SesController: NotFoundException (404)
    else Identity found
        alt csName non-blank
            SesService->>ConfigSetStore: getConfigurationSet(csName, region)
            alt Config set not found
                SesService-->>SesController: NotFoundException (404)
            end
        end
        SesService->>IdentityStore: put(identity with csName or null)
        SesController-->>Client: "200 {}"
    end

    Client->>SesController: POST /v2/email/outbound-emails (no ConfigurationSetName)
    SesController->>SesService: sendEmail(source, ..., null, region)
    SesService->>SesService: resolveDefaultConfigurationSet(null, source, region)
    SesService->>IdentityStore: get(identityKey for email)
    alt Email identity has default CS
        SesService-->>SesService: "effectiveConfigSet = identity.configurationSetName"
    else Fall through to domain identity
        SesService->>IdentityStore: get(identityKey for domain)
        alt Domain identity has default CS
            SesService-->>SesService: "effectiveConfigSet = domain.configurationSetName"
        else No default
            SesService-->>SesService: "effectiveConfigSet = null"
        end
    end
    SesService->>SesService: validateConfigurationSet(effectiveConfigSet)
    SesService->>SesService: publishSendEvents(effectiveConfigSet, ...)
    SesController-->>Client: "200 {MessageId}"
Loading

Reviews (4): Last reviewed commit: "docs(ses): clarify default config-set pr..." | Re-trigger Greptile

@okinaka okinaka force-pushed the feat/ses-v2-put-email-identity-config-set branch from 3bc88aa to dccdf40 Compare July 2, 2026 23:49
okinaka added 3 commits July 3, 2026 15:46
…ES v2

Add the v2 PutEmailIdentityConfigurationSetAttributes API and wire an email
identity's default configuration set into the send path so its event
destinations fire. This includes Raw sends with no explicit FromEmailAddress:
the configuration set is re-resolved from the MIME "From" header once parsed,
so an omitted-sender Raw send still routes through the identity default.
Unknown identities surface the AWS-matched error message.
@okinaka okinaka force-pushed the feat/ses-v2-put-email-identity-config-set branch from dccdf40 to 848af96 Compare July 3, 2026 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants