Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e1691cd
add resend support v0
hfellerhoff Apr 14, 2026
324c4bc
Merge remote-tracking branch 'origin/main' into subscription-tweaks
hfellerhoff Apr 16, 2026
0296aee
better subscription dialog ui
hfellerhoff Apr 16, 2026
3c85808
allow bulk toggle on/off
hfellerhoff Apr 16, 2026
07b2c23
add bulk select with mouse
hfellerhoff Apr 16, 2026
79348f3
add item limits in dialog chips
hfellerhoff Apr 16, 2026
cc5cc8c
better chip pagination, don't overflow responsive dialog
hfellerhoff Apr 16, 2026
a67c3fa
state improvements, add success screen
hfellerhoff Apr 16, 2026
a2ce78b
ui tweaks, improvements
hfellerhoff Apr 17, 2026
9d0decc
surface errors for background refresh, better loading state post upgrade
hfellerhoff Apr 17, 2026
f474f03
update subscription dialog
hfellerhoff Apr 17, 2026
b920e66
update plans, subscription ui
hfellerhoff Apr 17, 2026
60686f1
subscription tweaks
hfellerhoff Apr 17, 2026
ad8f790
update subscription copy, checkpoint of gating refreshes at user level
hfellerhoff Apr 17, 2026
2f0ddf0
add kv caching, better webhook handling
hfellerhoff Apr 19, 2026
eb6ed29
dialog tweaks, checkpoint before plan changes
hfellerhoff Apr 19, 2026
ad4c6c3
good subscription dialog state
hfellerhoff Apr 19, 2026
1ddb3cc
fix support email
hfellerhoff Apr 19, 2026
427ecc0
code review changes, cleanup
hfellerhoff Apr 19, 2026
d9acf40
track pending plan in ui
hfellerhoff Apr 20, 2026
33ed22e
combine switch dialogs into main subscription dialog
hfellerhoff Apr 20, 2026
81ca811
extensive subscription dialog qa
hfellerhoff Apr 20, 2026
c6ee055
add relase notes
hfellerhoff Apr 20, 2026
37f59a8
actually generate the migrations
hfellerhoff Apr 20, 2026
f891089
code review changes
hfellerhoff Apr 20, 2026
6212719
extract out server-side logic
hfellerhoff Apr 20, 2026
d263a86
fix flaky test
hfellerhoff Apr 20, 2026
6681df2
code review
hfellerhoff Apr 21, 2026
e8602ba
back off on error
hfellerhoff Apr 21, 2026
4a7aa7f
add initial error tracking support
hfellerhoff Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ BETTER_AUTH_SECRET=
# OAUTH_SCOPES=openid email profile
# OAUTH_PKCE=false

# Email
# Not required, but needed for functions like sending password reset emails.
# Email (optional — needed for password reset and email verification)
# FROM_EMAIL_ADDRESS is required for email sending to work.
# If both provider keys are set, Resend takes priority.
FROM_EMAIL_ADDRESS=
VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS=

RESEND_API_KEY=
SENDGRID_API_KEY=

# Integrations
Expand All @@ -29,3 +34,9 @@ INSTAPAPER_OAUTH_SECRET=
# Analytics
VITE_PUBLIC_UMAMI_WEBSITE_ID=
VITE_PUBLIC_UMAMI_SRC=

# Error Tracking (Sentry/GlitchTip)
VITE_PUBLIC_SENTRY_DSN_WEB=
VITE_PUBLIC_SENTRY_SECURITY_ENDPOINT_WEB=
SENTRY_DSN_BACKEND=
SENTRY_SECURITY_ENDPOINT_BACKEND=
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ Serial takes a model of progressive enhancement for features. The app can run wi

### Email support (for password reset, etc)

- Create an account on [Sendgrid](https://sendgrid.com/en-us)
- Set up a mailing address
- Add your `SENDGRID_API_KEY` to `.env` or your host's environment variables UI.
Serial supports [Resend](https://resend.com) and [SendGrid](https://sendgrid.com/en-us) as email providers. Only one is used at a time — if both keys are set, Resend takes priority.

- **Resend**: Create an account, add your `RESEND_API_KEY` to `.env` or your host's environment variables UI.
- **SendGrid**: Create an account, set up a mailing address, add your `SENDGRID_API_KEY` to `.env` or your host's environment variables UI.

### Instapaper integration

Expand Down
3 changes: 3 additions & 0 deletions docker-compose.arm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ services:
NODE_ENV: production
DATABASE_URL: http://libsql:8080
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
FROM_EMAIL_ADDRESS: ${FROM_EMAIL_ADDRESS}
RESEND_API_KEY: ${RESEND_API_KEY}
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS: ${VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS}
INSTAPAPER_OAUTH_ID: ${INSTAPAPER_OAUTH_ID}
INSTAPAPER_OAUTH_SECRET: ${INSTAPAPER_OAUTH_SECRET}
depends_on:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.build-arm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ services:
NODE_ENV: production
DATABASE_URL: http://libsql:8080
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
FROM_EMAIL_ADDRESS: ${FROM_EMAIL_ADDRESS}
RESEND_API_KEY: ${RESEND_API_KEY}
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS: ${VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS}
INSTAPAPER_OAUTH_ID: ${INSTAPAPER_OAUTH_ID}
INSTAPAPER_OAUTH_SECRET: ${INSTAPAPER_OAUTH_SECRET}
depends_on:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.build-cloud.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ services:
DATABASE_URL: ${DATABASE_URL}
DATABASE_AUTH_TOKEN: ${DATABASE_AUTH_TOKEN}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
FROM_EMAIL_ADDRESS: ${FROM_EMAIL_ADDRESS}
RESEND_API_KEY: ${RESEND_API_KEY}
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS: ${VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS}
INSTAPAPER_OAUTH_ID: ${INSTAPAPER_OAUTH_ID}
INSTAPAPER_OAUTH_SECRET: ${INSTAPAPER_OAUTH_SECRET}
ports:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ services:
NODE_ENV: production
DATABASE_URL: http://libsql:8080
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
FROM_EMAIL_ADDRESS: ${FROM_EMAIL_ADDRESS}
RESEND_API_KEY: ${RESEND_API_KEY}
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS: ${VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS}
INSTAPAPER_OAUTH_ID: ${INSTAPAPER_OAUTH_ID}
INSTAPAPER_OAUTH_SECRET: ${INSTAPAPER_OAUTH_SECRET}
depends_on:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.cloud.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ services:
DATABASE_URL: ${DATABASE_URL}
DATABASE_AUTH_TOKEN: ${DATABASE_AUTH_TOKEN}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
FROM_EMAIL_ADDRESS: ${FROM_EMAIL_ADDRESS}
RESEND_API_KEY: ${RESEND_API_KEY}
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS: ${VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS}
INSTAPAPER_OAUTH_ID: ${INSTAPAPER_OAUTH_ID}
INSTAPAPER_OAUTH_SECRET: ${INSTAPAPER_OAUTH_SECRET}
ports:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ services:
NODE_ENV: production
DATABASE_URL: http://libsql:8080
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
FROM_EMAIL_ADDRESS: ${FROM_EMAIL_ADDRESS}
RESEND_API_KEY: ${RESEND_API_KEY}
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS: ${VITE_PUBLIC_SUPPORT_EMAIL_ADDRESS}
INSTAPAPER_OAUTH_ID: ${INSTAPAPER_OAUTH_ID}
INSTAPAPER_OAUTH_SECRET: ${INSTAPAPER_OAUTH_SECRET}
depends_on:
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
"dev:db:test:self-hosted": "turso dev --db-file serial-test-self-hosted.db --port 8082",
"dev:migrate:test:main": "DATABASE_URL=http://127.0.0.1:8081 BETTER_AUTH_SECRET=test-secret-key-for-main-tests pnpm schema:migrate",
"dev:migrate:test:self-hosted": "DATABASE_URL=http://127.0.0.1:8082 BETTER_AUTH_SECRET=test-secret-key-for-self-hosted-tests pnpm schema:migrate",
"dev:test:main": "concurrently --kill-others \"pnpm dev:db:test:main\" \"pnpm dev:migrate:test:main && DATABASE_URL=http://127.0.0.1:8081 BETTER_AUTH_SECRET=test-secret-key-for-main-tests BETTER_AUTH_BASE_URL=http://localhost:3002 VITE_PUBLIC_IS_MAIN_INSTANCE=true POLAR_ACCESS_TOKEN=test-token-for-e2e POLAR_WEBHOOK_SECRET=test-webhook-secret POLAR_STANDARD_MONTHLY_PRODUCT_ID=test-standard-monthly POLAR_STANDARD_ANNUAL_PRODUCT_ID=test-standard-annual POLAR_PRO_MONTHLY_PRODUCT_ID=test-pro-monthly POLAR_PRO_ANNUAL_PRODUCT_ID=test-pro-annual vite dev --port 3002\"",
"dev:test:main": "concurrently --kill-others \"pnpm dev:db:test:main\" \"pnpm dev:migrate:test:main && DATABASE_URL=http://127.0.0.1:8081 BETTER_AUTH_SECRET=test-secret-key-for-main-tests BETTER_AUTH_BASE_URL=http://localhost:3002 VITE_PUBLIC_IS_MAIN_INSTANCE=true POLAR_ACCESS_TOKEN=test-token-for-e2e POLAR_WEBHOOK_SECRET=test-webhook-secret POLAR_STANDARD_SMALL_QUOTA_MONTHLY_PRODUCT_ID=test-standard-small-monthly POLAR_STANDARD_SMALL_QUOTA_ANNUAL_PRODUCT_ID=test-standard-small-annual POLAR_STANDARD_MEDIUM_QUOTA_MONTHLY_PRODUCT_ID=test-standard-medium-monthly POLAR_STANDARD_MEDIUM_QUOTA_ANNUAL_PRODUCT_ID=test-standard-medium-annual POLAR_STANDARD_LARGE_QUOTA_MONTHLY_PRODUCT_ID=test-standard-large-monthly POLAR_STANDARD_LARGE_QUOTA_ANNUAL_PRODUCT_ID=test-standard-large-annual POLAR_PRO_MONTHLY_PRODUCT_ID=test-pro-monthly POLAR_PRO_ANNUAL_PRODUCT_ID=test-pro-annual vite dev --port 3002\"",
"dev:test:self-hosted": "concurrently --kill-others \"pnpm dev:db:test:self-hosted\" \"pnpm dev:migrate:test:self-hosted && DATABASE_URL=http://127.0.0.1:8082 BETTER_AUTH_SECRET=test-secret-key-for-self-hosted-tests BETTER_AUTH_BASE_URL=http://localhost:3001 VITE_PUBLIC_IS_MAIN_INSTANCE=false vite dev --port 3001\"",
"dev:migrate": "pnpm schema:migrate",
"dev:atomic": "vite dev",
"dev:polar": "polar listen http://localhost:3000/api/auth/polar/webhooks",
"dev": "concurrently --kill-others \"pnpm dev:db\" \"pnpm dev:migrate && pnpm dev:atomic\"",
"dev:email": "email dev --dir ./src/emails --port 4000",
"prebuild": "pnpm typecheck && pnpm format && pnpm lint",
Expand Down Expand Up @@ -86,6 +87,8 @@
"@radix-ui/react-tooltip": "^1.2.8",
"@react-email/components": "^1.0.11",
"@sendgrid/mail": "^8.1.6",
"@sentry/browser": "^10.49.0",
"@sentry/node": "^10.49.0",
"@t3-oss/env-core": "^0.13.11",
"@tanstack/query-async-storage-persister": "^5.96.2",
"@tanstack/react-query": "^5.96.2",
Expand All @@ -94,6 +97,7 @@
"@tanstack/react-start": "1.167.16",
"@tanstack/react-table": "^8.21.3",
"@tanstack/zod-adapter": "1.166.9",
"@upstash/redis": "^1.37.0",
"better-auth": "^1.5.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down Expand Up @@ -124,6 +128,7 @@
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"resend": "^6.11.0",
"rss-parser": "^3.13.0",
"sonner": "^2.0.7",
"superjson": "^2.2.6",
Expand Down
Loading
Loading