Commit Graph
77 Commits
Author SHA1 Message Date
james.maesandCodex 9767cef03c ci: use the shared branch-aware CodeQL worker
Co-Authored-By: Codex <noreply@openai.com>
2026-10-21 10:02:07 -05:00
james.maesandCodex f93480b521 ci: adopt central orb 0.7.12 and stable gates (#35)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 03:32:44 -05:00
james.maesandCodex 2bbe208421 ci: centralize rc release completion in qqq orb (#34)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 02:23:32 -05:00
james.maesandCodex bd2724b384 ci: adopt shared orb 0.7.11 (#33)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 00:55:45 -05:00
james.maes 64cb2448cf Merge pull request #31 from QRun-IO/feature/central-ci
ci: adopt shared QQQ orb standard
2026-10-20 18:57:24 -05:00
james.maesandCodex 2fc7853ec1 ci: adopt verified central QQQ orb 0.7.3
Co-Authored-By: Codex <noreply@openai.com>
2026-10-20 18:52:13 -05:00
james.maes faad539c60 Merge pull request #30 from QRun-IO/feature/crm-template-owner
feat(templates): support checked CRM template handoff
2026-10-19 09:34:35 -05:00
james.maesandCodex e789f5bd83 feat(templates): support checked CRM template handoff
Keep template rendering and delivery ownership in email, with caller-scoped permissions and checked replay provenance.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-19 09:31:02 -05:00
james.maes 755a1e2465 Merge pull request #29 from QRun-IO/feature/readiness-says-where
fix(health): email.readiness says what is missing and where to set it
2026-10-16 11:51:33 -05:00
james.maesandClaude Opus 5.5 0c3c1564c3 fix(health): email.readiness says what is missing and where to set it
The readiness message was only "Email configuration incomplete" plus a code.
It now names each gap in plain words with the Email app path to fix it, and
notes that transactional email is unaffected by marketing-only gaps. The
failedChecks detail is unchanged; howToFix carries the same text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 11:36:58 -05:00
james.maes 68137d6fff Merge pull request #28 from QRun-IO/fix/email-health-tile-wording
fix(email): worded health tile instead of raw UNKNOWN
2026-10-16 10:20:09 -05:00
james.maesandClaude Opus 5.5 ba033f90bc fix(email): worded health tile instead of the raw UNKNOWN enum
The Email health tile showed the HealthStatus name (UP/DEGRADED/DOWN/UNKNOWN). It now shows Healthy / Degraded / Down, and "Not checked yet" when no
check has reported, with the check cadence in the context line. The drill to health_check is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:13:24 -05:00
james.maes 33b70be266 Merge pull request #26 from QRun-IO/ci/release-rc-secrets-inherit
ci(release-rc): pass secrets so the shared workflow can sign the RC tag
2026-10-16 08:04:53 -05:00
james.maesandClaude Opus 5.5 d11e38139c ci(release-rc): pass secrets to the shared workflow so it can sign the RC tag
QRun-IO/.github#2 adds a tag-and-release job that signs vX with the
QRUN_TAG_SIGNING_KEY secret after Maven Central confirms the artifact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:20:34 -05:00
james.maes a9415f93d9 Merge pull request #25 from QRun-IO/fix/producer-version-from-build
fix: qbit metadata version comes from the build
2026-10-15 01:42:56 -05:00
james.maesandClaude Opus 5.5 50f31a9040 fix: the qbit metadata version comes from the build, not a hardcoded constant
The producer read a literal version, so released artifacts reported a stale
or -SNAPSHOT version. qbit-email.properties (version=${project.version}) is
Maven-filtered and the producer loads it; tests assert producer == bundle ==
artifact version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:24:08 -05:00
james.maes 640e37f7c2 Merge pull request #23 from QRun-IO/fix/bundle-version-assertion
fix(test): compare the help bundle version with the qbit version
2026-10-15 01:14:50 -05:00
james.maesandClaude Opus 5.5 0cd1ee0c09 fix(test): assert the bundle version shape; the producer metadata version is a constant
The qbit metadata version is hardcoded (0.1.0), so it cannot be compared with
the filtered bundle version. Check that the bundle resolved a real
x.y.z[-SNAPSHOT|-RC.n] version instead of a literal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:12:32 -05:00
james.maesandClaude Opus 5.5 b3e3b87899 fix(test): compare the help bundle version with the qbit version, not a SNAPSHOT literal
The producer test pinned the bundle version to 0.1.0-SNAPSHOT, so the
0.1.0-RC.7 release build failed (bundle and qbit both 0.1.0-RC.6 locally).
Compare the bundle with the produced qbit's version instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:02:41 -05:00
james.maes 5b21576a0b Merge pull request #22 from QRun-IO/fix/help-bundle-version
fix(help): help bundle version comes from the build, not a hardcoded SNAPSHOT
2026-10-14 22:37:20 -05:00
james.maesandClaude Opus 5.5 156ca565fc fix(help): the help bundle carries the artifact version (Maven-filtered) instead of a hardcoded SNAPSHOT
On release branches the bundle's version and the producer's differed, so the
bundle test that asserts they match failed, and released bundles recorded
SNAPSHOT as their provenance. help/*.yaml is now filtered:
version: ${project.version}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:07:55 -05:00
james.maes 7cfafd7d2f Merge pull request #16 from QRun-IO/feature/health-checks
feat(health): register email provider and readiness checks with core central health
2026-10-14 21:27:01 -05:00
james.maesandClaude Opus 5.5 e88216de7e Merge origin/develop into feature/health-checks; pin QQQ 4.1.0-RC.8
RC.8 ships the central health kernel (QRun-IO/qqq#1207).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:18:26 -05:00
james.maes 0d326948fa Merge pull request #20 from QRun-IO/feature/help-bundle
feat(help): qbit-email help bundle via QQQ help bundles, with coverage gate
2026-10-14 20:45:30 -05:00
james.maesandClaude Opus 5.5 6dd1a00fbb feat(help): ship the qbit-email help bundle through QQQ help bundles, with coverage gate
Host-neutral help (deployment-specific facts removed), registered with
HelpBundles, gated by HelpBundleAssertions. QQQ 4.1.0-RC.7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:27:27 -05:00
james.maes 388f3eeade Merge pull request #17 from QRun-IO/feature/foreign-keys
Foreign keys between the email tables, with an orphan-safe upgrade
2026-10-14 18:00:28 -05:00
james.maesandClaude Opus 5.5 534d61c19a Merge origin/develop into feature/foreign-keys
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>
2026-10-14 17:50:14 -05:00
james.maes 28d0178a66 Merge pull request #19 from QRun-IO/feature/tables-populated-defaults
Every Email table used, seeded or explained; setup card and help
2026-10-14 17:31:48 -05:00
james.maesandClaude Opus 5.5 21d754d68c docs(help): reword the stand-in note in the help bundle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:23:56 -05:00
james.maesandClaude Opus 5.5 fb31d1e542 feat: starter brand, setup card and table help so no config table starts empty
Adds email-defaults.changelog.sql (non-default neutral brand, only when no brand
exists; tested through real Liquibase), help for the app and every table, a
Setup card on the home, guardrail tests and docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:13:37 -05:00
james.maesandClaude Opus 5.5 6e9351e8e3 fix(templates): use a template's owner qbit as the message sender
email_template.ownerQbit was stored but never read. Messages sent from a
template now name its owner qbit as senderQbit unless the request names one,
the built-in secure-message template sets it, and the field is read-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:13:36 -05:00
james.maes ccb256769c Merge pull request #18 from QRun-IO/feature/email-job-effect-target
feat: EmailJobEffectTarget, so durable jobs can send email through the outbox
2026-10-14 16:43:57 -05:00
james.maesandClaude Opus 5.5 2a2d69c5f6 feat: EmailJobEffectTarget, so durable jobs can send email through the outbox
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>
2026-10-14 16:33:37 -05:00
james.maesandClaude Opus 5.5 877b6eb6fb feat(ddl): foreign keys between the email tables, with an orphan-safe upgrade
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>
2026-10-14 16:30:06 -05:00
james.maesandClaude Opus 5.5 0122fb9fc1 docs: email health checks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:39:34 -05:00
james.maesandClaude Opus 5.5 fea8f8d445 refactor(email): home shows the central health status instead of its own provider health list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:39:34 -05:00
james.maesandClaude Opus 5.5 5be0e68ebb feat(health): register email provider and readiness checks with core central health
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:39:33 -05:00
james.maes 303848a86b Merge pull request #14 from QRun-IO/feature/orb-0.6.9
ci: qqq-orb 0.6.9 (refresh SNAPSHOT dependencies)
2026-10-14 10:46:00 -05:00
james.maes 3168b1979b Merge pull request #15 from QRun-IO/feature/readiness-checks
feat(api): structured readiness checks
2026-10-14 10:16:03 -05:00
james.maesandClaude Opus 5.5 28b964ab17 feat(api): structured readiness checks
EmailApiReadiness carries failedChecks (EmailApiReadinessCheck) next to
the plain reasons, so callers such as qbit-marketing can tell a missing
unsubscribe key from a missing provider without parsing text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:12:31 -05:00
james.maesandClaude Opus 5.5 30352cf7c5 ci: qqq-orb 0.6.9 (refresh SNAPSHOT dependencies)
Moves qqq-orb@0.6.8 to qqq-orb@0.6.9, which passes --update-snapshots so cached SNAPSHOT dependencies are re-checked on every build (QRun-IO/qqq-orb#22).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:06:58 -05:00
james.maes d2bddc968a Merge pull request #13 from QRun-IO/feature/public-api
feat: public api package for other qbits
2026-10-14 09:44:03 -05:00
james.maesandClaude Opus 5.5 acff558a9a feat(api): fold EmailEffects into EmailApi
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>
2026-10-14 09:41:29 -05:00
james.maesandClaude Opus 5.5 a3ea38eb7e Merge develop (fixed Message-ID, EmailEffects) into feature/public-api
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:35:39 -05:00
james.maes 6eb5854fb4 Merge pull request #12 from QRun-IO/feature/effect-message-id
feat: fixed Message-ID per email and an effect handoff API for durable jobs
2026-10-14 09:30:01 -05:00
james.maesandClaude Opus 5.5 dc23fbdae7 test: PHI canary uses non-hex letters, so hex digests can't match by chance
PhiCanaryEgressTest checks that no five-character window of the canary
appears in any output. The canary's random part was a hex UUID, so a window
could match a SHA-256 body hash or a Message-ID by chance (CI hit "ebf93" in
a body hash). The random part is now mapped from hex to the letters g-v, so a
match can only mean the canary really leaked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:27:00 -05:00
james.maesandClaude Opus 5.5 5f6b661d1a feat: public api package for other qbits
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>
2026-10-14 09:02:41 -05:00
james.maesandClaude Opus 5.5 40f6935c19 feat: fixed Message-ID per email and an effect handoff API for durable jobs
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>
2026-10-14 08:58:59 -05:00
james.maes 351a8070db Merge pull request #11 from QRun-IO/feature/sender-addresses
feat: provider sender addresses with contingent from-address pickers
2026-10-14 07:34:27 -05:00
james.maes f87e70dd53 Merge pull request #9 from QRun-IO/feature/phase-c-tracking
feat: phase (c) - delivery tracking, suppression and one-click unsubscribe
2026-10-14 07:34:15 -05:00