Skip to main content

SQS job boundary

The API has one shared SQS client for submitting messages to separate standard queues for email and push jobs. There are no concrete feature-owned producers, payload contracts, consumers, or worker entrypoints yet. No job is submitted until a feature slice calls the client with an explicit destination.

SQS is reserved for asynchronous jobs that need durable queue delivery. Live notifications use Realtime event channels and are not a queue. Neither a job message nor a broadcast is the system of record for business data.

Producer flow​

The API requires these runtime values:

VariablePurpose
AWS_REGIONRegion containing both queues.
SQS_EMAIL_QUEUE_URLFull HTTPS URL of the email jobs queue.
SQS_PUSH_QUEUE_URLFull HTTPS URL of the push jobs queue.

The API workload needs sqs:SendMessage permission on both queues. The AWS SDK uses its default credential provider chain, so a workload IAM role can supply credentials without static access keys. For local development, an AWS profile or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY can supply credentials; AWS_SESSION_TOKEN is needed when those keys are temporary. These credential variables are not required API configuration.

The shared producer accepts an email or push destination plus an already serialized message body. It sends the body unchanged to only the matching queue and returns after SQS acknowledges the send. Unsupported runtime destinations are rejected before an SQS request. A failed send reports a safe error without exposing the body, queue URL, or credentials. Both destinations use one AWS SDK client, one Region, and the same credential chain. The adapter relies on the AWS SDK's transport retry behavior and does not add an application-level retry loop.

A feature that submits a job should:

  1. declare a job-publisher port in its domain boundary;
  2. define the job payload in packages/contracts/src/<slice>/;
  3. implement the port with an adapter inside the slice's infrastructure/ that fixes the appropriate destination and calls the shared SQS producer;
  4. persist the business state before publishing the recorded job event.

A failed persistence write publishes nothing. Application use cases depend on the port, not on the AWS SDK or an SQS message type. The owning use case decides how an enqueue failure affects its result. A database commit and an SQS send are not atomic; use an outbox in a future feature if losing a job after a committed write would be unacceptable.

Delivery and lifecycle​

Both configured queues are standard SQS queues, so future consumers must tolerate duplicate and out-of-order delivery. The shared producer does not define a payload contract, envelope, FIFO message groups, retry policy, or dead-letter policy.

Constructing the singleton does not contact SQS, and API startup does not probe the queue. The first enqueue is the first SQS request, so an unavailable queue does not prevent the HTTP listener from starting; it causes that enqueue to fail safely.

During normal shutdown and partial-startup cleanup, the API stops the HTTP listener before it destroys the SQS client. Concurrent or repeated shutdown requests share the same cleanup operation, so the client is destroyed once.

Consumer flow​

Consumers belong to the owning feature slice's events/ directory and call the slice's application use cases. A future apps/api/src/app/worker.ts composition root will construct adapters and register only the consumers the process serves.

Consumer deletion, retry policy, dead-letter queues, idempotency, and observability must be decided and documented when the first concrete job is implemented. They are intentionally not specified here without a workload.

The reusable Resend email sender is available for a future delivery runtime. It does not add an SQS consumer or define an email-job body.