Skip to content

Phase 3: Refactor SesWebhookController into service classes #11

@dkwiebe

Description

@dkwiebe

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

  • SnsPayloadParser has unit tests for both payload formats and error paths
  • SesEventProcessor has unit tests per event type (can reuse data from existing feature tests)
  • All existing webhook feature tests continue to pass
  • Controller/Job body is under ~30 lines of logic

Metadata

Metadata

Assignees

No one assigned

    Labels

    architectureCode quality and architecturephase-3Architecture cleanup

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions