Class MailOutbox

java.lang.Object
com.onec.mail.outbox.MailOutbox

public class MailOutbox extends Object
Side-effect queue for outbound mail. Lives in its own table onec_mail_outbox rather than the framework's domain-event outbox: mail is a command-to-execute, not an event-to-broadcast, and giving it a separate table avoids racing with the Kafka relay over the shared onec_outbox.

Works on both supported engines: PostgreSQL gets the lock-free fast paths (ON CONFLICT, UPDATE ... RETURNING with SKIP LOCKED); H2 — used by single-node dev/demo apps — gets portable equivalents selected via SqlDialect.

  • Constructor Details

    • MailOutbox

      public MailOutbox(org.jdbi.v3.core.Jdbi jdbi)
  • Method Details

    • initSchema

      public void initSchema()
    • enqueue

      public UUID enqueue(String payload, String provider)
    • enqueue

      public UUID enqueue(String payload, String provider, String idempotencyKey)
      Enqueues a message. When idempotencyKey is non-blank and a row with that key already exists, the existing id is returned and no new row is inserted, so retried application logic can't double-send.
    • claimBatch

      public List<MailOutbox.Pending> claimBatch(int limit, Duration leaseTimeout)
      Atomically claims up to limit due messages for this worker, flipping them NEW -> SENDING. On PostgreSQL this is a single statement; FOR UPDATE SKIP LOCKED lets concurrent relays (multiple app instances) grab disjoint batches instead of all selecting the same rows and sending duplicates. On H2 — which has neither UPDATE ... RETURNING nor SKIP LOCKED — the claim is a transactional SELECT ... FOR UPDATE followed by the status flip: concurrent claimers serialise on the row locks rather than skipping, which is fine for the single-node setups H2 backs. Rows stuck in SENDING longer than leaseTimeout — a worker that crashed mid-send — are reclaimed on either engine.
    • markDispatched

      public void markDispatched(UUID id)
    • recordFailure

      public void recordFailure(UUID id, int attempts, String error, LocalDateTime nextAttempt, boolean exhausted)