odysseus-dev/odysseus · error · Error

ntfy reminder was not sent

Error message

ntfy reminder was not sent

What it means

Surfaced by the settings UI during the test-save of notification settings: the server-side ntfy push failed (bad topic name, unreachable ntfy server, or rejected request), so the test reminder could not be delivered even though the settings themselves saved.

Solutions

  1. Inspect data.ntfy_error in the response for the exact upstream failure
  2. Verify the ntfy server URL/topic configured on the backend
  3. Curl the ntfy endpoint from the server host to confirm egress
  4. Add a fuller fallback message so missing ntfy_error still points at config

Example fix

// before
        if (channelSel.value === 'ntfy' && !data.ntfy_sent) {
          throw new Error(data.ntfy_error || 'ntfy reminder was not sent');
        }

// after
        if (channelSel.value === 'ntfy' && !data.ntfy_sent) {
          throw new Error(data.ntfy_error || 'ntfy reminder was not sent (verify ntfy URL/topic)');
        }
Defensive patterns

Strategy: try-catch

Validate before calling

// client can only pre-check channel selection; ntfy reachability is server-side
if (channelSel.value !== 'ntfy') return;

Try / catch

if (channelSel.value === 'ntfy' && !data.ntfy_sent) { throw new Error(data.ntfy_error || 'ntfy reminder was not sent (verify ntfy URL/topic)'); }

Prevention

When it happens

Trigger: POST test returns 200 with {ntfy_sent: false} — ntfy server URL wrong or unreachable, topic rejected, rate-limited, or the server's HTTP POST to ntfy threw and was captured into ntfy_error.

Common situations: Self-hosted ntfy instance down or DNS stale; ntfy.sh rate limits; typo'd topic name; server egress blocked; ntfy_error absent so the generic message hides the cause.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/f7944f8febceff14. Report an issue: GitHub.

Appendix: source

Thrown at static/js/settings.js:2710

            channel: channelSel.value,
            // Mirror the in-UI AI Synthesis toggle + persona so the test never
            // races a pending save and lets the user preview changes before
            // hitting Save.
            llm_synthesis: !!(llmToggle && llmToggle.checked),
            llm_persona: (personaSel && personaSel.value) || '',
            ...(channelSel.value === 'webhook' ? {
              webhook_integration_id: webhookIntgSel?.value || '',
              webhook_payload_template: webhookTemplateIn?.value.trim() || '',
            } : {}),
          }),
        });
        const data = await res.json();
        if (!res.ok) throw new Error(data.detail || 'Server error');
        if (channelSel.value === 'email' && !data.email_sent) {
          throw new Error(data.email_error || 'Email reminder was not sent');
        }
        if (channelSel.value === 'ntfy' && !data.ntfy_sent) {
          throw new Error(data.ntfy_error || 'ntfy reminder was not sent');
        }
        if (channelSel.value === 'webhook' && !data.webhook_sent) {
          const activeChannel = data.channel ? ` (server used channel: "${data.channel}")` : '';
          throw new Error((data.webhook_error || 'Webhook reminder was not sent') + activeChannel);
        }
        let status = 'Delivered via ' + channelSel.value;
        if (data.synthesis) status += ' (AI: "' + data.synthesis.slice(0, 60) + '...")';
        if (data.email_sent) status += ' — email sent';
        if (data.ntfy_sent) status += ' — ntfy sent';
        if (data.webhook_sent) status += ' — webhook sent';
        if (testMsg) { testMsg.textContent = status; testMsg.style.color = 'var(--green, #50fa7b)'; }
        // Also fire a browser notification so user can see it
        if ('Notification' in window && Notification.permission === 'granted') {
          try {
            new Notification('Test Reminder', {
              body: data.synthesis || 'This is a test reminder.',
              tag: 'reminder-test',
              icon: '/static/favicon.ico',

View on GitHub (pinned to f9235ebbf1)