feat(convex-email): finish the Convex component - #126
Conversation
Close out the remaining gaps in @opencoredev/convex-email: - Track delivery state from webhooks: provider event names (Resend email.*, Postmark RecordType, Mailgun event-data, and common generic keys) are normalized onto a per-email deliveryStatus of delivered, bounced, or complained, plus deliveredAt. Bounces and complaints are sticky so out-of-order webhook retries never hide a bounce. - Widen generic webhook parsing: MessageID (Postmark), event-data.id and event-data.message.headers["message-id"] (Mailgun) now link deliveries to stored emails. - Fix the enqueueBatch perf bug: read the shared config document once per batch instead of once per message; enqueueEmail still reads it itself when called standalone. - Make setConfig replace the stored config instead of patch-merging, so omitted fields are cleared; merge semantics made it impossible to unset defaultFrom or cleanupAfterDays. - Type the ConvexEmail client surface: send/sendBatch/status/ listEvents/cancel/getConfig/processWebhook now return typed results (new ConvexEmailDoc, ConvexEmailEventDoc, ConvexEmailDeliveryStatus exports) instead of unknown. - Add lifecycle tests: batch enqueue with config defaults, in-batch idempotency dedupe, retry backoff to terminal failure, cancel, missing-from error, config replace semantics, and webhook delivery status for Resend, Postmark, and Mailgun shapes. - Update the README and docs page to match. The hand-maintained _generated stubs derive types from schema.ts, so the schema additions need no Convex codegen or deployment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Generated-By: PostHog Code Task-Id: ec2538f2-5c80-4142-b4b3-b1a53394862e
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR completes the Convex email component delivery-state workflow. The main changes are:
Confidence Score: 5/5The changes appear safe to merge based on the touched component behavior, optional schema additions, and expanded tests described for the queue, webhook, config, and client surfaces. No blocking code issues were identified in the finalized review, and the package changes are covered by focused tests plus type/build validation across the affected workspace and docs app.
What T-Rex did
Reviews (2): Last reviewed commit: "fix(convex-email): gate bounce mapping o..." | Re-trigger Greptile |
…rk bounce Type Review findings on the webhook normalization: - Mailgun sends event "failed" (permanent_fail is only the webhook config name), so bounces never mapped. Map failed + severity "permanent" to bounced; temporary failures stay stored-but-unmapped since Mailgun retries. - Postmark fires RecordType "Bounce" for soft bounces too, which made a Transient bounce permanently poison deliveryStatus. Only permanent Types (HardBounce, BadEmailAddress, ManuallyDeactivated) map to bounced now. - Document that bounced/complained are sticky against delivered, but between themselves the most recent webhook wins. - Accept Postmark's numeric uppercase ID as a dedupe delivery id instead of falling back to the body hash. Adds 6 tests (35 total) and aligns README + docs page with the exact mapping. Generated-By: PostHog Code Task-Id: ec2538f2-5c80-4142-b4b3-b1a53394862e
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
Merge brings in delivery status tracking (#126), recipient variables (#125), scheduled sends (#127), the fumadocs SSR README (#121), AGENTS.md updates, and Homebrew 0.6.5. Conflicts resolved by keeping the humanized prose and main's feature facts; the field-support matrix re-verified cell-for-cell against SUPPORTED_MESSAGE_FIELDS, including the new Send at column. Also: clarify one-tag semantics (Postmark keeps name:value, Mailtrap and Lettermint keep only the value), document sendBulk in the adapter contract, add all_recipients_failed to the errors page, note per-recipient idempotency suffixing and per-recipient hook firing, cross-link recipientVariables and sendAt from the quickstart and landing page, and pre-add the telemetry disclosure sections to both READMEs ahead of the telemetry PR. Generated-By: PostHog Code Task-Id: ec2538f2-5c80-4142-b4b3-b1a53394862e
Part of #106.
The package turned out to be in better shape than the empty issue suggested — the queue, retries, fallback plumbing, idempotency, test mode, cron recovery, and cleanup all exist and work. What was actually unfinished, from a full audit of the package against its README and docs page:
What was missing
messageId/message_id, so Postmark (MessageID) and Mailgun (event-data.message.headers["message-id"]) payloads could never link back to a stored email.enqueueBatchperf bug. The config document was read once per message inside the loop instead of once per batch.setConfigmerge trap. Patch-merge semantics meant a config field could never be cleared (the validator can't carry an explicitundefined), so a staledefaultFromorcleanupAfterDayswas permanent.ConvexEmailmethod returnedunknown.from, batch happy path, in-batch idempotency, or any webhook-to-email linking.What this PR does
email.*, PostmarkRecordType, Mailgunevent-data, common generic keys) onto a per-emaildeliveryStatusofdelivered/bounced/complained, plusdeliveredAt. Only permanent failures count asbounced: Mailgunfailedevents are gated onseverity: "permanent"and PostmarkBouncerecords on a permanentType(HardBounce,BadEmailAddress,ManuallyDeactivated) — soft/temporary failures stay stored-but-unmapped so a retried send can still land asdelivered. Bounces and complaints are sticky againstdelivered(out-of-order webhook retries never overwrite them with a late "delivered"); between themselves the most recent webhook wins. Unknown events (opens, clicks) stay in the event history without touchingdeliveryStatus.webhookDeliveriesand widens generic message-id extraction so Postmark- and Mailgun-shaped payloads link to their emails. Postmark's numeric uppercaseIDnow serves as the dedupe delivery id instead of falling back to a body hash.enqueueBatchto one read per batch (enqueueEmailstill reads it standalone) — same shape as the unmerged fix ontembo/optimize-performance-bottlenecks.setConfigto replace the stored config document; omitted fields are now cleared, documented in README and docs.send→Promise<string>,status→Promise<ConvexEmailDoc | null>, etc., with newConvexEmailDoc,ConvexEmailEventDoc, andConvexEmailDeliveryStatusexports.Deliberately not done
.changeset/config.jsonsetsprivatePackages: { version: false, tag: false }, and I verified locally thatchangeset versionneither bumps nor consumes a changeset referencing this package — it would linger in.changeset/forever and keep triggering the release workflow's path filter. No merged changeset has referenced convex-email since it went private in Version packages #84. When the package goes public for npm Trusted Publishing, this work should ride in that first public version bump.sendBatchis a queue-level batch (one durable row per message); per-provider batch APIs are the SDK's concern.@opencoredev/email-sdk.verifycallback by design.Maintainer notes
_generatedstubs derive their types fromschema.ts(DataModelFromSchemaDefinition) and useanyApi, so the schema additions (emails.deliveryStatus,emails.deliveredAt,webhookDeliveries.event) flow through without regen. All new fields are optional, so existing deployments need no migration.setConfigis a behavior change (replace vs merge) — safe while the package is private/unpublished, and now documented.Validation
packages/convex-email:check-types,build,bun test→ 35 pass / 0 fail (repeated runs)apps/fumadocs:types:checkpasses.launch-smoke/convex-email-smoke.test.tspasses against the rebuilt packagebunx oxlintclean on all touched filesCreated with PostHog Code