diff --git a/README.md b/README.md index 1b04755..4f6f479 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A Nostr email notification server. Receives events pushed from relays via NIP-9a ## Architecture ``` -Flotilla ──HTTP──▶ Mailship (POST /subscription/email) +Flotilla ──HTTP──▶ Mailship (PUT /subscription/email) │ NIP-98 auth │ returns {key, callback} │ @@ -43,8 +43,10 @@ Flotilla ──HTTP──▶ Mailship (POST /subscription/email) ## API -### POST /subscription/email -Register for email digests. +### PUT /subscription/email +Idempotently register or update an email subscription. Re-sends the confirmation +email only when the subscription is new or the email address changed; a frequency +change keeps the existing confirmation. ``` Body: { email, frequency, pubkey } @@ -52,6 +54,14 @@ Auth: NIP-98 (planned) Response: { key, callback } ``` +### GET /subscription/email?pubkey=... +Look up an existing subscription, so clients can avoid re-registering (and +re-confirming) when settings haven't changed. Returns 404 if none exists. + +``` +Response: { key, callback, email, frequency, confirmed } +``` + ### DELETE /subscription/:key Unsubscribe. diff --git a/src/actions.ts b/src/actions.ts index 459cd01..ceec6fe 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -23,8 +23,12 @@ export const registerSubscription = instrument( const sub = await db.insertSubscription(pubkey, email, frequency) const callback = `${process.env.BASE_URL}/notify/${sub.id}` - // Send confirmation email - await mailer.sendConfirm(sub) + // Only send a confirmation when the subscription is new, unconfirmed, or + // its email address changed. An already-confirmed, unchanged subscription + // (or one where only the frequency changed) skips it. + if (!sub.confirmed_at) { + await mailer.sendConfirm(sub) + } return { key: sub.key, callback } }, diff --git a/src/database.ts b/src/database.ts index b50fe56..268675a 100644 --- a/src/database.ts +++ b/src/database.ts @@ -101,7 +101,26 @@ export const insertSubscription = instrument( const existing = await getSubscriptionByPubkey(pubkey) if (existing) { - // Update existing + // If nothing changed, keep confirmation and don't re-validate + if (existing.email === email && existing.frequency === frequency) { + return assertResult(parseSubscription(existing)) + } + + // Update existing. Only a change of email address invalidates the + // existing confirmation (the new address must be verified); changing + // the frequency keeps it confirmed. + if (existing.email === email) { + return assertResult( + parseSubscription( + await get( + `UPDATE subscriptions SET frequency = ?, unsubscribed_at = NULL + WHERE pubkey = ? RETURNING *`, + [frequency, pubkey], + ), + ), + ) + } + return assertResult( parseSubscription( await get( @@ -113,7 +132,6 @@ export const insertSubscription = instrument( ) } - // Create new return assertResult( parseSubscription( await get( diff --git a/src/pages/confirm-error.html b/src/pages/confirm-error.html index 240a99b..072f48a 100644 --- a/src/pages/confirm-error.html +++ b/src/pages/confirm-error.html @@ -1,34 +1,63 @@
-