Repository navigation
feat(import): record inbound emails for readers and admins - #249
Merged
Merged
Conversation
Every attempt the worker makes at an inbound email now upserts one row in `inbound_emails` (keyed by Resend's email id): sender, subject, received time, status, DMARC result, notebook titles and what was created. A thrown attempt is recorded as `retrying`, or `failed` on the last of the 5, with the error's first line. Created counts add up across retries; recording is best effort and never fails or repeats an import. - Settings → Email import lists the reader's latest 50 emails (only mail their verified addresses sent), with a hint per status and no error text. - /dash/<you>/admin/inbound-emails lists mail no verified address sent, with the full sender, reply limit, DMARC and attempts; both admin pages get a nav. - Account deletion removes the reader's rows and the reader-less rows from their verified addresses. - formatDateTime shows a time in UTC, labelled as such. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFQ6mgubacomHnKckCPg4A
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Main split the importer (saving into save.ts, logging into log.ts) and added the `unpinned` DMARC result. saveNotebook now adds what each chunk saves to the record's counts from save.ts; the local Outcome type stays replaced by ImportOutcome. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFQ6mgubacomHnKckCPg4A
Contributor
Review: record inbound emailsOverall this is a clean, well-scoped change. The best-effort recording, the first-line-only Worth addressing
Minor
TestsCoverage is good: every outcome, retry then success, final failure, and the best-effort write failure. A test for the redelivery case above and for the spoofed-DMARC listing would round it out. 🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Email sent to
/api/v3/inbound/resendleft no record anywhere except OTel logs (with the sender masked) andemail_bindings.last_imported_at. Readers couldn't tell whether an export worked, and admins couldn't see mail from unverified senders. This PR records every inbound email the worker handles, shows readers their own, and gives admins a page for mail no verified address sent.What changes
New table
inbound_emails(drizzle/0007_inbound_emails.sql, generated by drizzle-kit)email_id.detail, DMARC result, notebook titles, highlight count, clippings created, notes created, attempts.user_idis the reader whose verified address sent the email. Mail from a pending address gets no reader, because anyone can claim an address.Recording (
src/server/email-import/records.ts,import.ts,worker.ts)run()fills in afactsobject as it goes.importEmail()upserts the row after every attempt.retrying, orfailedon the last of the 5. The worker now passes the attempt number and whether it is the last one.detailkeeps only the first line of the error, because Drizzle puts the query's parameters on the second line.user_idkeeps its first value if a retry no longer finds the binding.Reader view
Admin view
/dash/<you>/admin/inbound-emails, with the sameisAdminguard and offset pagination as the existing admin page.NavTabsnav, rendered after the guard and not from a layout.Account deletion removes the reader's rows, plus rows with no reader that came from the reader's verified addresses.
Other
formatDateTime()(date and time, in UTC and labelled as such).docs/email-import.mdandCLAUDE.mdupdated.Deploy note
Run
pnpm db:migratebefore deploying. The startup schema check exits wheninbound_emailsis missing.Testing
pnpm exec vitest run: 88 files, 679 tests pass. New and extended tests:import.test.ts: a row for each outcome, retrying then succeeding, the final failure, and an import that still succeeds when the row can't be written.records.test.ts: upsert merging, clipping, list scoping.database.test.ts: which rows account deletion removes.inbound-email-list.test.tsx: what readers and admins each see.format-date.test.ts.pnpm lintandpnpm format:checkare clean.pnpm typecheck:serveris clean.pnpm typecheckreports only the 4 asset-module errors thatmainalready has here, because the gitignorednext-env.d.tsis missing.🤖 Generated with Claude Code
https://claude.ai/code/session_01GFQ6mgubacomHnKckCPg4A
Generated by Claude Code