Skip to content

SQS Queue Handlers

defineSqsHandler creates Lambda handlers triggered by SQS queues. Import from @mantleframework/core.

The factory iterates over each record in the batch, calls your handler once per record, and returns a SQSBatchResponse with batchItemFailures for any records that threw. Failed records are retried without reprocessing successful ones.

Pattern

typescript
import { defineSqsHandler } from '@mantleframework/core'

interface SyncMessage {
  userId: string
  items: { id: string; value: number }[]
}

const sqs = defineSqsHandler<SyncMessage>({
  queue: 'sync-queue',
  batchSize: 10,
  operationName: 'ProcessSyncQueue',
})
export const handler = sqs(async (record, metadata) => {
  // record.body is typed as SyncMessage
  await DataQueries.upsertBatch(record.body.items, record.body.userId)
})

Options

OptionTypeDefaultDescription
queuestring--Queue name — matches config.queues[].name for CLI infra wiring
batchSizenumber--SQS batch size (CLI extraction marker, not runtime)
parseBodybooleantrueParse record body as JSON
operationNamestringfunction nameName for metrics and tracing
loggingLoggingConfig--Fixture logging configuration
timeoutnumber--Lambda timeout in seconds
memorySizenumber128Lambda memory in MB
reservedConcurrencynumber--Reserved concurrent executions
ephemeralStoragenumber512Ephemeral storage in MB
deadLetterQueueboolean | { targetArn? }--true = auto-generate SQS DLQ
retryAttemptsnumber--Max retry attempts (0-2)

Per-Record Callback

The inner handler is called once per SQS record and receives SqsRecordContext<TBody>:

typescript
interface SqsRecordContext<TBody = unknown> {
  messageId: string               // SQS message ID
  body: TBody                     // Parsed JSON (if parseBody: true) or raw string
  attributes: SQSRecordAttributes
  messageAttributes: SQSMessageAttributes
  receiptHandle: string           // Receipt handle for manual deletion
  rawRecord: SQSRecord            // The raw SQS record
}

The second argument is WrapperMetadata:

typescript
interface WrapperMetadata {
  traceId: string
  correlationId: string
}

Retry vs Discard

By default any record that throws is added to batchItemFailures and SQS retries it. To permanently discard a record without retrying, return without throwing — or catch the error and handle it explicitly.

typescript
export const handler = sqs(async (record, metadata) => {
  try {
    await processRecord(record.body)
  } catch (err) {
    if (isUnrecoverable(err)) {
      // Log and discard — do NOT rethrow
      logger.error('Unrecoverable record, discarding', { messageId: record.messageId })
      return
    }
    throw err  // Rethrow to trigger SQS retry for this record
  }
})

Queue Config

The queue option must match a queue name declared in mantle.config.ts:

typescript
export default defineConfig({
  queues: [
    { name: 'sync-queue' },
  ],
})

The CLI uses this to wire the SQS trigger and IAM permissions automatically.

Raw Body (no JSON parsing)

Set parseBody: false to receive the raw string body:

typescript
const sqs = defineSqsHandler<string>({ queue: 'raw-queue', parseBody: false })
export const handler = sqs(async (record) => {
  const raw = record.body  // string
})

File Location

SQS handlers live under src/lambdas/sqs/<HandlerName>/index.ts:

src/lambdas/sqs/
  ProcessSyncQueue/
    index.ts
  HandleNotifications/
    index.ts

See Also