diff --git a/packages/api/src/adapter-aws/AwsSesAdapter.test.ts b/packages/api/src/adapter-aws/AwsSesAdapter.test.ts index 5a97d5f..80b2611 100644 --- a/packages/api/src/adapter-aws/AwsSesAdapter.test.ts +++ b/packages/api/src/adapter-aws/AwsSesAdapter.test.ts @@ -59,6 +59,18 @@ describe('AwsSesAdapter', () => { expect(codes).toEqual(['869472', '4410', null]) }) + test('lists the newest email first', async () => { + const adapter = new AwsSesAdapter('http://floci.test', async () => jsonResponse({ + messages: [ + {Id: 'old', Timestamp: '2026-10-23T21:12:41.732Z'}, + {Id: 'newest', Timestamp: '2026-10-24T05:00:00.000Z'}, + {Id: 'middle', Timestamp: '2026-10-23T21:56:04.866Z'}, + ], + })) + + expect((await adapter.list()).map((resource) => resource.id)).toEqual(['newest', 'middle', 'old']) + }) + test('filters messages by subject, sender, and recipients', async () => { const adapter = new AwsSesAdapter('http://floci.test', async () => jsonResponse({ messages: [ diff --git a/packages/api/src/adapter-aws/AwsSesAdapter.ts b/packages/api/src/adapter-aws/AwsSesAdapter.ts index 385502b..14878b0 100644 --- a/packages/api/src/adapter-aws/AwsSesAdapter.ts +++ b/packages/api/src/adapter-aws/AwsSesAdapter.ts @@ -47,7 +47,7 @@ export class AwsSesAdapter implements CloudServiceAdapter { async list(query: ResourceQuery = {}): Promise { const messages = await this.fetchInbox() - const resources = messages.map(messageToResource) + const resources = messages.map(messageToResource).sort(newestFirst) return filterBySearch(resources, query.search) } @@ -137,6 +137,10 @@ function findCode(body: string | null | undefined): string | null { return body?.match(CODE_PATTERN)?.[1] ?? null } +function newestFirst(a: CloudResource, b: CloudResource): number { + return (b.createdAt ?? '').localeCompare(a.createdAt ?? '') +} + function filterBySearch(resources: CloudResource[], search?: string): CloudResource[] { const normalized = search?.trim().toLowerCase() if (!normalized) return resources diff --git a/packages/frontend/src/components/DynamicResourceView.tsx b/packages/frontend/src/components/DynamicResourceView.tsx index b2c418f..2cb5c9f 100644 --- a/packages/frontend/src/components/DynamicResourceView.tsx +++ b/packages/frontend/src/components/DynamicResourceView.tsx @@ -632,7 +632,6 @@ export function DynamicResourceView({ className={`resource-workbench${activeSelected && !showDatabaseSnapshots ? " with-inspector" : ""}`} >
- {cloud === 'aws' && service === 'email' && } {isAwsDatabase && (
{activeSelected && !showDatabaseSnapshots && (
diff --git a/packages/frontend/src/components/SesConsolePanel.tsx b/packages/frontend/src/components/SesConsolePanel.tsx index 803f126..808d52e 100644 --- a/packages/frontend/src/components/SesConsolePanel.tsx +++ b/packages/frontend/src/components/SesConsolePanel.tsx @@ -62,7 +62,7 @@ export function SesConsolePanel({runtimeReachable}: {runtimeReachable: boolean}) return

SES identities

Verify senders

Floci verifies identities immediately for AWS-compatible workflows. Local sending is not sandbox-gated.

{event.preventDefault(); if (identity.trim()) createIdentity.mutate()}}> setIdentity(event.target.value)} placeholder="sender@example.test or example.test" required/>
{identities.data?.map((item) =>
{item.IdentityName}{item.IdentityType} · {item.VerifiedForSendingStatus ? 'verified for sending' : 'not verified'}
)}
-

Compose

Send through SES

Simulator recipients produce documented delivery, bounce, and complaint events locally.

{SIMULATORS.map((address) => )}
{event.preventDefault(); send.mutate()}}>{mode === 'simple' && <>