Built on QQQ 4.1.0-RC.7, which has the durable jobs side-effects outbox.
- EmailJobEffectTarget implements core's EffectTarget.
- Delivery hands the email to qbit-email's own outbox with the effect's
idempotency key, so it lands exactly once per key, with the fixed
Message-ID. The result is SENT with the message id, or FAILED when
refused.
- It runs as the job owner (JobSessions.forJob). If the owner can no longer
send, the result is FAILED, never UNKNOWN.
- Reconcile looks the message up by key.
- Email effects through it are IDEMPOTENT_EXTERNAL.
- EmailQBitProducer registers it as target "email" when durable jobs are
enabled (EmailJobEffectTarget.register).
- docs/DURABLE-JOBS.md and the README describe declaring and emitting email
effects.
verify: 301 tests (14 new), checkstyle clean, coverage 95.4% / 100%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EmailApi.sendOnce(request, effectKey) with EmailApiEffectOutcome and
EmailApiEffectResult, lookups by idempotency key and RFC Message-ID, and
rfcMessageId on EmailApiMessage, so other qbits have one public surface.
EmailEffects and its types stay as internal, deprecated delegates; the
durable-jobs guide, README and DESIGN 2.5 point at EmailApi.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For the durable jobs outbox (QRun-IO/qqq#1191, spec section 2.13):
- Each queued message stores one RFC 5322 Message-ID (email_message.rfc_message_id,
unique), derived from the idempotency key (else a stable per-message seed),
and reuses it on every attempt. SMTP, Postmark, SendGrid and Mailgun send it.
SES receives it in the raw MIME but replaces it; this is documented, and
X-QQQ-Email-Id stays on every transport.
- EmailEffects.send(request, effectKey) hands an email to qbit-email's outbox
exactly once per key: QUEUED_OR_DUPLICATE, or REFUSED (suppressed or
invalid; never retried).
- findByIdempotencyKey / findByRfcMessageId look an email up for a reconcile.
- docs/DURABLE-JOBS.md has the outcome mapping and the binding class to add
when QQQ RC.7 ships (EmailJobEffectTarget implements core's EffectTarget;
no SNAPSHOT dependency now).
- Postgres upgrade SQL for existing installs; checkstyle now also covers test
sources (the repo standard), with the existing violations fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>