louislam/uptime-kuma · error · Error

SMSEagle API returned an empty response

Error message

SMSEagle API returned an empty response

What it means

Thrown by the SMSEagle apiv2 path when the HTTP response is non-200 OR no recipients landed in 'queued' status, AND resp.data is an empty array (length 0). The v2 /api/v2 endpoints return an array of per-recipient status objects; an empty array means the gateway accepted the request but produced zero deliverable status entries, so there is nothing to report.

Source

Thrown at server/notification-providers/smseagle.js:127

                    if (notification.smseagleMsgType === "smseagle-ring") {
                        endpoint = "/calls/ring";
                    } else if (notification.smseagleMsgType === "smseagle-tts") {
                        endpoint = "/calls/tts";
                    } else if (notification.smseagleMsgType === "smseagle-tts-advanced") {
                        endpoint = "/calls/tts_advanced";
                        postData["voice_id"] = notification.smseagleTtsModel ?? 1;
                    }
                }

                let resp = await axios.post(notification.smseagleUrl + "/api/v2" + endpoint, postData, config);

                const queuedCount = resp.data.filter((x) => x.status === "queued").length;
                const unqueuedCount = resp.data.length - queuedCount;

                if (resp.status !== 200 || queuedCount === 0) {
                    if (!resp.data.length) {
                        throw new Error("SMSEagle API returned an empty response");
                    }
                    throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
                }

                if (unqueuedCount) {
                    return `Sent ${queuedCount}/${resp.data.length} Messages Successfully.`;
                }

                return okMsg;
            }
        } catch (error) {
            this.throwGeneralAxiosError(error);
        }
    }
}

module.exports = SMSEagle;

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. In the notification config, ensure at least one of smseagleRecipientTo, smseagleRecipientContact, or smseagleRecipientGroup is populated with valid values.
  2. Confirm the device firmware supports apiv2 and the {smseagleUrl}/api/v2 base path resolves (visit {smseagleUrl}/api/v2/ping).
  3. Check that numeric recipient IDs (contacts/groups) are valid integers — non-numeric strings become NaN after .map(Number) and are silently dropped.
  4. Reproduce the POST with curl using the access-token header to inspect the raw array the device returns.

Example fix

// before
if (resp.status !== 200 || queuedCount === 0) {
    if (!resp.data.length) {
        throw new Error("SMSEagle API returned an empty response");
    }
    throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
}
// after (surface which recipient source was missing for faster diagnosis)
const hasRecipient = postData.to?.length || postData.contacts?.length || postData.groups?.length;
if (resp.status !== 200 || queuedCount === 0) {
    if (!resp.data.length) {
        throw new Error(`SMSEagle API returned an empty response (recipients sent: ${!!hasRecipient})`);
    }
    throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure at least one recipient source is set before calling the v2 endpoint
const hasRecipient =
    (notification.smseagleRecipientTo && notification.smseagleRecipientTo.trim()) ||
    (notification.smseagleRecipientContact && notification.smseagleRecipientContact.trim()) ||
    (notification.smseagleRecipientGroup && notification.smseagleRecipientGroup.trim());
if (!hasRecipient) {
    throw new Error("At least one SMSEagle recipient (to/contact/group) is required for apiv2");
}

Type guard

/** True when the v2 response is a non-empty array of status objects. */
function isSmseagleV2StatusArray(data) {
    return Array.isArray(data) && data.length > 0 && data.every((x) => x && typeof x.status === "string");
}

Try / catch

try {
    const resp = await axios.post(url, postData, config);
    if (!Array.isArray(resp.data)) {
        throw new Error(`SMSEagle v2 returned non-array body: ${JSON.stringify(resp.data)}`);
    }
    if (resp.status !== 200 || resp.data.filter((x) => x.status === "queued").length === 0) {
        if (resp.data.length === 0) throw new Error("SMSEagle API returned an empty response");
        throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
    }
} catch (err) {
    this.throwGeneralAxiosError(err);
}

Prevention

When it happens

Trigger: POST to {smseagleUrl}/api/v2/messages/sms or /api/v2/calls/* with an empty to/contacts/groups payload, or the device returning 200 with [] because all recipients were filtered out server-side. Also reachable when the device returns a non-2xx status with no body.

Common situations: smseagleRecipientTo/Contact/Group fields left blank while apiv2 is selected, comma-separated IDs that all fail to parse via .map(Number) producing NaN entries the device rejects, or a firmware bug returning [] on internal failure.

Related errors


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/ab6193da3e748d0a. Report an issue: GitHub.