myna newsletter: crawlproof tracking, A/B variants, stats, fail-closed opt-outs - #92
Merged
Merged
Conversation
…to main) #90 was merged into feat/newsletter after #89 had already been squashed into main, so its commit (fc102a0) never reached main. This is that commit cherry-picked onto main, with the conflicts against #91 resolved: sync and the daemon report re-subscribes from myna cloud as well as unsubscribes from both sources. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A list send, the daemon's newsletter job and myna newsletter sync now treat a failed myna cloud pull the way they already treated crawlproof: the send stops before anyone is mailed, the daemon job fails that turn, and sync exits 1. syncAllUnsubscribes tries every configured source and names each that failed. A --to test copy still goes out, with a warning. Error text is plain; the HTTP helper's dash is shown as a colon. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
newsletters.json keeps, per contact, the newest unsubscribe or re-subscribe applied from myna cloud or crawlproof, with the source's timestamp. A pulled event older than that is skipped, so a myna cloud re-subscribe is not undone by an older crawlproof unsubscribe that arrives in the same or a later pull, and the reverse. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 24, 2026
ralyodio
added a commit
that referenced
this pull request
Sep 24, 2026
Newsletter tracking through crawlproof, A/B subjects crossed with a rotating CTA set, per-variant stats, and opt-out pulls that fail closed (#92). Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
This replaces #90. #90 was merged into
feat/newsletterafter #89 had already been squash-merged into main (c6ff655), so its code (fc102a0) never reached main. This branch is based on main as it is now (82fca38, which includes #91). It brings #90 over and adds the fail-closed opt-out change.Commits
Port of myna newsletter: crawlproof tracking, A/B variants, stats (stacked on #89) #90 (747d398). This is fc102a0 cherry-picked onto main. The conflicts with 0.29.1: one click unsubscribes, and the page offers Re-subscribe #91 were resolved in 0.29.1: one click unsubscribes, and the page offers Re-subscribe #91's favour: its one-click unsubscribe, the Re-subscribe page, and
optInon a re-subscribe all stay as they are.syncand the daemon job report re-subscribes as well. crawlproof's signed unsubscribe link is used only when tracking is on; otherwise the myna cloud link (ornewsletter.unsubscribeUrl) is used, as in 0.29.0: myna newsletter, with one-click unsubscribe on every issue #89/0.29.1: one click unsubscribes, and the page offers Re-subscribe #91.Fail closed on both opt-out sources (fb4cf56, fc07975). If the pull from the myna cloud inbox or from crawlproof fails:
myna newsletter sync/sync-optoutsprintsFAILEDfor that source and exits 1.syncAllUnsubscribestries every configured source and names each one that failed. A--totest copy still goes out, with a warning. Error text is plain, with no em dashes: the HTTP helper's dash is shown as a colon.The newest opt-out or re-subscribe wins across both sources (e8d3882).
newsletters.jsonnow keepsoptChanges: for each contact, the newest unsubscribe or re-subscribe applied from either source, with that source's timestamp. A pulled event older than that is skipped. So a myna cloud re-subscribe is not undone by an older crawlproof unsubscribe that arrives in the same pull or a later one, and the reverse also holds.What #90 adds (unchanged from #90)
myna newsletter track set <24-hex id> [--secret] | status [--check] | off. The secret is kept in the vault.List-Unsubscribeuse crawlproof's signed one-click unsubscribe link.--subject-b) crossed with a CTA set (--cta-set, defaultdefault). Each person's variant is fixed bysha256(issueId:email).{{cta}}marks where the CTA goes.cta list|add|rmmanages the sets.--servicesets the footer line to "an account at ".send --tosends one test copy as variant A. A list send needs--yes.--max-per-dayoverrides the daily cap for one run, and sends are paced.stats <id>shows each variant's results and names the leader.What the data cannot tell
optChangesentry. The first event pulled for a contact after upgrading is applied as-is, with nothing older to compare against.myna newsletter unsubscribe,myna contacts optout) is not recorded inoptChanges. As in 0.29.1: one click unsubscribes, and the page offers Re-subscribe #91, a later myna cloud re-subscribe from that person lifts it.Tests
packages/core/test/newsletter-tracking.test.tshas 16 tests. They run against a fake SMTP server, with a stubbed fetch standing in for crawlproof and myna cloud. They cover:--totest copy going out with a warning;syncAllUnsubscribestrying every source;No real mail is sent.
bun x tsc --noEmit -p tsconfig.json: cleanbun run test: 554 pass, 19 skip, 0 fail🤖 Generated with Claude Code