Release staging to main: v1.0 artifacts, billing overage, Compose Redis/SMTP#662
Release staging to main: v1.0 artifacts, billing overage, Compose Redis/SMTP#662jaeyunha wants to merge 16 commits into
Conversation
* Implement metered overage billing * Fix metered overage billing follow-up
Release-readiness refresh — 2026-06-18Release package prepared for current staging head, but not merge-ready yet because GitHub reports conflicts against Verified live refs / PR shape
Raw commit inventoryValidation evidence
Mergeability blockerGitHub reports No conflict-resolution branch update was pushed because main promotion / merge work requires explicit Jaeyun authorization. Caveats
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 34cc90d6c6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| 5. Update `plans.stripe_price_id` and `plans.stripe_overage_price_id` for the | ||
| matching paid public `plans.slug` rows. | ||
| 6. Run `bun run billing:preflight -- --check-db --strict` against the hosted DB. |
There was a problem hiding this comment.
Backfill existing Stripe subscriptions before soft caps
For existing paid subscribers created before this release, the old checkout path created Stripe subscriptions with only the base stripe_price_id; this cutover step only updates local plans rows, while the new checkout code attaches the metered Price only for future checkouts. In a staging→main cutover, those existing subscribers become soft-capped in OpenSend but still lack a metered Stripe subscription item to invoice the reported meter usage, so the runbook needs an explicit backfill/update of active Stripe subscriptions before enabling overage sends.
Useful? React with 👍 / 👎.
| const quotaPredicate = hardCapEmails | ||
| ? sql`${usagePeriods.emailsSent} + ${delta} <= ${limit}` | ||
| : sql`true`; |
There was a problem hiding this comment.
Keep caps until overage billing is configured
When BILLING_BACKEND=stripe is deployed before every existing paid plan has a non-empty stripe_overage_price_id populated (the migration leaves the new column nullable), this sqltrue`` branch removes the monthly cap for paid subscribers anyway, while /jobs/billing-overage only scans plans with an overage price and therefore skips null configurations. That lets existing paid accounts send unlimited over quota without any billable overage; keep the hard cap unless the plan is actually overage-configured, and mirror the same guard in the core `usageQuota` path.
Useful? React with 👍 / 👎.
Release package refresh needed
Live refs now:
Validation evidence for newest staging head:
Current classification: release PR #662 is stale/ |
|
Release refresh complete for existing PR #662 (no duplicate PR created).
Remaining gate: main merge/deploy/cutover still requires explicit Jaeyun authorization. Do not merge until that approval is given. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a2e6c107d7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| status: "failed", | ||
| lastError: truncateError(input.error), | ||
| updatedAt: input.now, | ||
| }) | ||
| .where(eq(billingOverageReports.id, input.report.id)); |
There was a problem hiding this comment.
Guard stale failures from overwriting reported rows
When a Stripe call outlives REPORT_LEASE_MS, another scheduler run can reclaim the same sending row and successfully mark it reported. If the original attempt then errors, this update matches only by id and changes that already reported row back to failed, so a later scan retries the already reported range before moving on to new overage. Add a status/lease/attempt guard (or claim token) before setting failed.
Useful? React with 👍 / 👎.
Prepare v1.0.0 release artifacts
# Conflicts: # drizzle/meta/_journal.json # public/docs/knowledge-base/quotas-rate-limits-production-access.md # src/components/landing/pricing-page.tsx
|
Refreshed PR #662 after #664 merged.
Validation:
No |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 42dc762335
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| build: | ||
| context: . | ||
| target: runner | ||
| image: ghcr.io/namuh-eng/opensend:v1.0.0 |
There was a problem hiding this comment.
Keep Compose source-built until GHCR tags are live
If this release PR is merged before the v1.0.0 tag workflow has successfully pushed the GHCR images, the documented default docker compose up -d path will fail while pulling ghcr.io/namuh-eng/opensend:v1.0.0 instead of building from the checked-out source; the new workflow only publishes these tags on a later v* tag event, so keep the default source-built (or otherwise gate this switch on already-published artifacts) until those images are guaranteed to exist.
Useful? React with 👍 / 👎.
[P1] Secrets bootstrap: generate complete .env
|
Refreshed existing staging→main release PR after #665 landed on Live refs at refresh:
New staging content included since prior refresh:
Validation run on refreshed release worktree:
No browser/E2E, live Stripe, live SMTP, GHCR publish, tag, release, main merge, or deploy was performed in this refresh. Remaining gate: main merge/deploy/cutover still requires explicit Jaeyun authorization. Do not merge until that approval is given. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1cfdda5a9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| try { | ||
| await input.stripe.billing.meterEvents.create( | ||
| { | ||
| event_name: input.meterEventName, |
There was a problem hiding this comment.
Reuse the stored meter name when retrying reports
When a billing_overage_reports row is retried after STRIPE_OVERAGE_METER_EVENT_NAME changes, this sends the old reportKey/idempotency key to the new meter because the persisted meterEventName is ignored. In that configuration-change scenario Stripe can reject the idempotency replay as different parameters, or the usage can land on the wrong meter, leaving the outbox stuck or usage unbilled; include the row's meterEventName in the claimed report and use it for the retry.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| await mkdir(path.dirname(outputPath), { recursive: true }); | ||
| await writeFile(outputPath, renderOpenSendEnvFile(env), { mode: 0o600 }); |
There was a problem hiding this comment.
Tighten permissions when overwriting generated env files
When users run bun run setup -- --force against an existing .env, writeFile(..., { mode: 0o600 }) does not change the permissions of an already-created file, so a previously copied or editor-created 0644 .env remains readable after the command writes fresh database/auth/DKIM secrets. Explicitly chmod the output after writing, or write a new temp file with restrictive permissions and rename it.
Useful? React with 👍 / 👎.
Adds production CloudWatch alarm manifesting, alerting runbook/docs, and alarm-friendly EMF metrics for queue publish, scheduler, worker, SES ingest, ECS, ALB, and DLQ coverage. Validated with PR CI plus targeted maintainer QA.
* Add deploy fallback runbook preflight * Fix deploy fallback preflight review comments * Fix deploy fallback preflight secret coverage * Check Docker ECR auth in deploy fallback preflight * Check ECS task definitions in deploy fallback preflight * Fix deploy fallback preflight contract * Validate scheduler base secrets in fallback preflight * fix: require app database metadata in deploy preflight * Fix fallback preflight Bun prerequisite * Tighten ingester fallback preflight metadata * fix deploy fallback app metadata preflight * Require active ECS services in deploy preflight * fix deploy fallback preflight blockers
* Add DB polling fallback for queued sends * Fix scheduled SQS claim race * Align DB polling fallback e2e status
Release PR #662 refresh evidence — 2026-06-19Refs
Raw origin/main..origin/staging inventory (13 commits)Raw origin/main..release/staging-main-20260618 inventory (16 commits)Refresh action
Local validation
Current GitHub PR state after push
Caveats / gate
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab83fee672
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| await emailRepo.update(email.id, { status: "processing" }); | ||
| const [claimedEmail] = await emailRepo.claimForSending(email.id); |
There was a problem hiding this comment.
Reclaim stale processing sends before skipping
When a worker crashes or is restarted after this claim has set the email row to processing but before the retry handler changes it back to queued/failed, the SQS redelivery or DB poller reaches this claim again and finds no queued/due scheduled row. processEmailSend then returns not_claimed, and the SQS path treats that as success and deletes the message, leaving the email permanently processing and never sent; add a processing lease/stale reclaim path or avoid deleting these retries until ownership is resolved.
Useful? React with 👍 / 👎.
| S3_BUCKET_NAME: external.S3_BUCKET_NAME?.trim() ?? "", | ||
| CLOUDFLARE_API_TOKEN: external.CLOUDFLARE_API_TOKEN?.trim() ?? "", | ||
| CLOUDFLARE_ZONE_ID: external.CLOUDFLARE_ZONE_ID?.trim() ?? "", | ||
| RATE_LIMIT_BACKEND: "disabled", |
There was a problem hiding this comment.
Keep generated Compose env on Redis
When users follow the documented quick start (bun run setup then docker compose up), this writes RATE_LIMIT_BACKEND=disabled into .env, and the app service's ${RATE_LIMIT_BACKEND:-redis} default is overridden by that non-empty value. The result is production Compose booting with rate limiting disabled even though the Redis service is started; omit this key from generated setup output or generate the Compose-safe Redis value so the documented path actually uses shared Redis.
Useful? React with 👍 / 👎.
Release package: staging → main (2026-06-18 refresh after #664)
Promote the current
origin/stagingrelease set intomainafter release authorization.Do not merge until Jaeyun explicitly authorizes main promotion/deploy. This PR is the prepared release package only.
Live ref verification
release/staging-main-20260618→main)origin/main:5f16631975efa211400dfda56b700746bb0d5083origin/staging:a249776c80949f40e6e7c7924d9093efac67384b42dc7623351f5aeb0871d20449fa06763e7c70fba249776c80949f40e6e7c7924d9093efac67384b42dc7623351f5aeb0871d20449fa06763e7c70fbmerges currentorigin/maininto the release branch so PR Release staging to main: v1.0 artifacts, billing overage, Compose Redis/SMTP #662 is no longer conflict-dirty.Raw commit inventory (
origin/main..origin/staging)Release branch inventory (
origin/main..release/staging-main-20260618)Upstream staging source PRs:
Implement metered overage billingComplete Compose Redis and SMTP relay stackPrepare v1.0.0 release artifactsConflict resolution applied
GitHub reported the stale release branch as
DIRTY. I resolved the realorigin/mainconflicts on the release branch without merging tomain:mainLite/51k pricing changes and combined them with staging's $0.85/1k overage language.0037_lite_tier_and_quota_20260617data migration.0037_real_hawkeyeto0038_real_hawkeyeand updated the Drizzle journal/snapshot path accordingly.mainimport-CSV/dashboard/learnings state through the merge parent, while retaining all staging release content.Changed surfaces reviewed
@opensend/core, and@opensend/ingesterversions set to1.0.0.docs/release-notes/v1.0.0.mdand release version sync tooling/tests..github/workflows/release.ymlfor tag-gated version/notes checks, multi-arch GHCR image publication, manifest inspection, and GitHub Release creation after image publication succeeds.docker-compose.local.ymlfor source-build local development.0037_lite_tier_and_quota_20260617retained.0038_real_hawkeyeon this release branch.Validation evidence
CI/pushonstaginga249776c80949f40e6e7c7924d9093efac67384bsuccessCI/pull_request42dc7623351f5aeb0871d20449fa06763e7c70fbsuccessgit diff --checkpassed.git diff --cached --checkpassed before conflict-resolution commit.make checkpassed: full-project typecheck + Biome check of 811 files.node tools/verify-release-version-sync.mjs --tag v1.0.0 --require-release-notespassed.bunx vitest run tests/release-version-sync.test.ts tests/deploy.test.ts tests/security-startup-checks.test.ts tests/billing-schema.test.ts tests/billing-plans-page.test.tsx tests/pricing-grid.test.tsxpassed: 6 files, 48 tests.bun run docs:checkpassed: 216 docs pages.ruby -e 'require "yaml"; ARGV.each { |f| YAML.load_file(f); puts "YAML OK #{f}" }' .github/workflows/release.yml docker-compose.yml docker-compose.local.ymlpassed.docker compose --env-file .env.example config --quietpassed.docker compose --env-file .env.example -f docker-compose.yml -f docker-compose.local.yml config --quietpassed.Caveats / not validated in this refresh session
v1.0.0, create a GitHub Release, publish GHCR images, merge tomain, deploy production, or authorize Stripe/SMTP cutover.Jaeyun authorization gate
Merge/main promotion/deploy gate: Jaeyun must explicitly authorize merging this staging→main release PR and any subsequent deploy/cutover.