From 609a2f65ea932c38034210e284d8f44a44621012 Mon Sep 17 00:00:00 2001 From: mplorentz Date: Tue, 22 Sep 2026 12:31:56 -0400 Subject: [PATCH] Email digest: GET-first restore on boot so reloads stop re-sending confirmations Mail.sync's boot reconcile now GETs the existing subscription (NIP-98 auth) before deciding what to do. When a subscription already exists for the pubkey with the same email and frequency, its key/callback are restored in memory and the PUT is skipped entirely. Only a matching subscription missing the requested params triggers a PUT, so a page refresh of an unconfirmed address no longer makes mailship re-send a confirmation email (previously each reload sent one). --- src/app/mail/index.ts | 57 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/app/mail/index.ts b/src/app/mail/index.ts index 69b356b6..5460681f 100644 --- a/src/app/mail/index.ts +++ b/src/app/mail/index.ts @@ -150,6 +150,42 @@ export class MailNotifications { return this._register } + // Fetch the existing subscription for this pubkey (if any) without creating + // or changing anything. Used at boot to restore state instead of re-PUTting, + // which would otherwise re-send a confirmation email for an unconfirmed + // address on every reload. + async lookup(): Promise< + | { + key: string + callback: string + email: string + frequency: string + confirmed: boolean + } + | undefined + > { + const $signer = app.get().user?.signer + if (!$signer || !EMAIL_NOTIFICATION_SERVER) return undefined + + try { + const authEvent = await $signer.sign( + await makeHttpAuth(buildUrl(EMAIL_NOTIFICATION_SERVER, "subscription", "email"), "GET"), + ) + const authHeader = makeHttpAuthHeader(authEvent) + + const res = await fetch(buildUrl(EMAIL_NOTIFICATION_SERVER, "subscription", "email"), { + method: "GET", + headers: {Authorization: authHeader}, + }) + + if (!res.ok) return undefined + + return await res.json() + } catch { + return undefined + } + } + async unregister() { const {key} = emailNotificationState.get() if (!key) return @@ -231,8 +267,27 @@ export class MailNotifications { reconciledPubkey = $pubkey + // GET-first: if a subscription already exists for this pubkey with the + // same email and cadence, restore its state (key/callback) instead of + // PUTting. A PUT here would make mailship re-send a confirmation email + // for an unconfirmed address on every app reload. Only register when + // there's nothing to restore or the settings changed. + const existing = await this.lookup() + try { - await this.register(emailAddress, emailFrequency) + if (!existing) { + await this.register(emailAddress, emailFrequency) + } else if (existing.email !== emailAddress || existing.frequency !== emailFrequency) { + await this.register(emailAddress, emailFrequency) + } else { + emailNotificationState.set({ + key: existing.key, + callback: existing.callback, + email: existing.email, + frequency: existing.frequency, + error: undefined, + }) + } } catch { return }