Summary
SesWebhookController (and the forthcoming ProcessSesWebhook Job from Phase 2) handles too many responsibilities: SNS envelope unwrapping, dual payload format parsing, synthetic message ID generation, email/recipient/event DB writes, open/click special-casing, and Sentry error capture. This makes the class hard to test in isolation and hard to extend when new SES event types are added.
Implementation
1. Create app/Services/SnsPayloadParser.php
Responsible for:
- Detecting SNS-wrapped vs. direct SES payload format
- Unwrapping the SNS
Message JSON string
- Computing the synthetic MD5
MessageId for direct-format events
- Returning a normalized canonical struct consumed by the processor
class SnsPayloadParser
{
public function parse(array $raw): ParsedSesPayload // throws on unknown format
}
2. Create app/Services/SesEventProcessor.php
Responsible for:
firstOrCreate logic for Email, EmailRecipient, RecipientEvent
- The
SES_EVENT_TYPE_TO_ENUM mapping
- Open/click recipient assignment logic
- Bounce/complaint/delivery-delay recipient extraction
class SesEventProcessor
{
public function process(Project $project, ParsedSesPayload $payload): void
}
3. Slim down SesWebhookController / ProcessSesWebhook
Inject both services; reduce the controller/job body to:
$parsed = $this->parser->parse($rawPayload);
$this->processor->process($project, $parsed);
Acceptance Criteria
Summary
SesWebhookController(and the forthcomingProcessSesWebhookJob from Phase 2) handles too many responsibilities: SNS envelope unwrapping, dual payload format parsing, synthetic message ID generation, email/recipient/event DB writes, open/click special-casing, and Sentry error capture. This makes the class hard to test in isolation and hard to extend when new SES event types are added.Implementation
1. Create
app/Services/SnsPayloadParser.phpResponsible for:
MessageJSON stringMessageIdfor direct-format events2. Create
app/Services/SesEventProcessor.phpResponsible for:
firstOrCreatelogic forEmail,EmailRecipient,RecipientEventSES_EVENT_TYPE_TO_ENUMmapping3. Slim down
SesWebhookController/ProcessSesWebhookInject both services; reduce the controller/job body to:
Acceptance Criteria
SnsPayloadParserhas unit tests for both payload formats and error pathsSesEventProcessorhas unit tests per event type (can reuse data from existing feature tests)