Keeps develop's liquibase-core test dependency next to the Postgres
Testcontainers dependencies, and both changelog entries. H2 tests load
src/test/resources/email-h2.sql (FKs in H2 syntax), never the Postgres DDL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Declare a named foreign key for every join and table possible-value field
between the ten email tables, plus suppression.source_message_id. Owned
rows cascade, providers/brands/templates with history are RESTRICT, and
optional pointers (template versions, sender address, suppression sources)
are SET NULL so message retention keeps working. The provider delete check
also covers attempts, events and inbox rows. The upgrade script quarantines
orphans before adding the keys; a Testcontainers guardrail test fails when
a new relation has no key.
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>
com.kingsrook.qbits.email.api is the stable contract for other qbits:
EmailApi (isInstalled, send, ensureTemplate, message lookups by id and
idempotency-key prefix with metadata only, sender addresses, default
provider, readiness per category) with its own request, result,
template, message and readiness types. A reflection test keeps internal
types out of every public signature. The README and DESIGN 2.5 document
the two supported routes: core SendMessageAction with the qbitEmail
provider for plain mail, and the api package for the rest; internal
packages are not API.
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>
email_provider_sender_address (child of the provider; admin-only;
domain, uniqueness and single-default checks). Sends may name a sender
row (withFromAddressId) or match one by text; rows belong to the
resolved provider, the default row wins, and the provider's from
address stays the fallback. Message rows record from_address_id.
Contingent possible-value filters: fromAddressId by providerId on both
send processes, on Send Test Email (seeded from the acted-on provider)
and on email_message; message ids on attempts and events by provider;
suppression source event by source message. One test per pair.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in phase (b); README and CHANGELOG combine both, DESIGN 7.3 keeps
the ClassifiedValueTokens wording with the env1 key id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Webhook inbox rows apply matched events independently; unmatched
events retry, then are skipped with a note instead of failing the row.
- Notification and marketing messages are never deleted by retention,
so unsubscribe links keep working; their bodies are still cleared.
- Events only suppress recipients of the matched message; an SES
bounce without recipients no longer suppresses every destination.
- SNS notifications outside a 1 h / +5 min window are refused, and
failed certificate fetches are cached for 5 minutes.
- EmailTemplateDefinition and EmailTemplates.ensure let other qbits
register their templates once without overwriting edits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps the polished home layout with phase (c)'s event-backed metrics.
Also fixes unsubscribe privacy: a message with an unsubscribe link goes
to exactly one recipient, its token binds that address, and the handler
suppresses only that address (DESIGN 8.2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase (c) of docs/DESIGN.md: the email_webhook_inbox and email_event
tables; a /qqq/hooks/email route provider with SES (SNS signature and
certificate host rules, subscription confirmation), Postmark (basic
auth), SendGrid (ECDSA) and Mailgun (HMAC with replay protection)
verification into a durable inbox; a claim-safe inbox processor that
maps provider events to email_event rows, message status and
suppressions; RFC 8058 one-click unsubscribe with a confirmation page;
the Reprocess action; and event-backed home metrics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase (b) of docs/DESIGN.md: HTTP JSON and MIME transports over
java.net.http with fixed HTTPS endpoints (Mailgun US and EU), error
mapping to permanent, transient (with Retry-After) and provider-level
failures, tracking flags and message streams, per-kind validation and
env-name allowlists, the Gmail SMTP preset, and docs/PROVIDERS.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase (a) part 2 of docs/DESIGN.md: the variable engine with escaping,
if/each and fallbacks; variable schemas; Markdown in the branded
responsive layout with dark-mode colours and contrast checks; HTML and
MJML kinds (MJML through a pluggable compiler at activation); versions
with New Version, Activate Version and Approve For PHI; templated sends
with record variables read as the sender; the classification stand-in
(core #1142); PII/PHI refusal; redacted variable snapshots with
ClassifiedValueTokens-shaped tokens and encrypted PHI bodies (stand-ins
for core #1161); the secure-message built-in; the preview widget; the
home dashboard; contingent possible values; and the canary-PHI egress
test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase (a) part 1 of docs/DESIGN.md: the email_ tables (provider,
message, attempt, template, version, brand, suppression) with Postgres
DDL; provider rows with env-var-name credentials; EmailService with
idempotency keys, suppression and same-transaction enqueue; the
dispatcher with unique-attempt claims, leases, backoff, dead letters,
rate limits and provider health; SMTP (jakarta.mail with auth, TLS and
timeouts) and SES (AWS SDK v2 sesv2 raw) transports; the QBIT_EMAIL
core messaging provider; Resend, Cancel, Send Now and Send Test Email
record actions; scheduled dispatch, maintenance and retention.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D11 records the owner rule that only the database and S3 hold data.
Section 7.3 follows secured-data section 14.5 (record-field and
domain-prefixed bindings, changed:v1 for low-entropy context values).
Section 8.3 uses UserPreferenceRequestContext.getUserEmailAddress().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Outbox on core's messaging SPI, providers as data rows (SMTP, SES,
Postmark, SendGrid, Mailgun, Gmail), Markdown and MJML/HTML templates
with versions and dark-mode-safe branding, delivery tracking through
signed webhooks, suppression, RFC 8058 unsubscribe, PHI rules, consumer
migrations and the inbound phase. Owner decisions (2026-10-12): all
recommendations accepted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apache-2.0 LICENSE and NOTICE, community pointer files, CHANGELOG,
CLAUDE.md and AGENTS.md, Maven wrapper, Checkstyle, dependabot,
CircleCI (qqq-orb 0.6.8, configure-package-install pre-step, GA-only
publish_release), the release-rc caller workflow, and a minimal
EmailQBitProducer and EmailQBitConfig on qbit-build-parent 2.0.0 with
the QQQ 4.1 BOM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>