From 5ec38d6136ab1349a137fd7fbb3a4d92d6f91d8a Mon Sep 17 00:00:00 2001
From: Manuel <30572287+manuel-rw@users.noreply.github.com>
Date: Wed, 1 Jul 2026 13:56:27 +0200
Subject: [PATCH] feat: add pushover integration
---
.../docs/docs/integrations/pushover/index.mdx | 42 +++++++++++++++++++
apps/docs/docs/integrations/pushover/index.ts | 8 ++++
.../docs/docs/widgets/notifications/index.mdx | 4 +-
.../src/docs/homarr-docs-sitemap.ts | 2 +
.../src/docs/integration-doc-slugs.ts | 1 +
packages/definitions/src/integration.ts | 9 ++++
packages/integrations/src/base/creator.ts | 2 +
packages/integrations/src/index.ts | 1 +
.../src/pushover/pushover-integration.ts | 38 +++++++++++++++++
.../src/pushover/pushover-schema.ts | 9 ++++
10 files changed, 115 insertions(+), 1 deletion(-)
create mode 100644 apps/docs/docs/integrations/pushover/index.mdx
create mode 100644 apps/docs/docs/integrations/pushover/index.ts
create mode 100644 packages/integrations/src/pushover/pushover-integration.ts
create mode 100644 packages/integrations/src/pushover/pushover-schema.ts
diff --git a/apps/docs/docs/integrations/pushover/index.mdx b/apps/docs/docs/integrations/pushover/index.mdx
new file mode 100644
index 000000000..7266b7321
--- /dev/null
+++ b/apps/docs/docs/integrations/pushover/index.mdx
@@ -0,0 +1,42 @@
+---
+title: 'Pushover'
+description: "Pushover is a service for receiving real-time push notifications on your Android, iPhone, iPad, and Desktop devices."
+hide_title: true
+---
+
+import { notificationsWidget } from '@site/docs/widgets/notifications';
+import { AddingIntegration } from '@site/src/components/integrations/adding';
+import { IntegrationHeader } from '@site/src/components/integrations/header';
+import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
+import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
+import { pushoverIntegration } from '.';
+
+
+
+### Widgets & Capabilities
+
+
+### Adding the integration
+
+
+### Secrets
+
diff --git a/apps/docs/docs/integrations/pushover/index.ts b/apps/docs/docs/integrations/pushover/index.ts
new file mode 100644
index 000000000..b4fe1193d
--- /dev/null
+++ b/apps/docs/docs/integrations/pushover/index.ts
@@ -0,0 +1,8 @@
+import { IntegrationDefinition } from "@site/src/types";
+
+export const pushoverIntegration: IntegrationDefinition = {
+ name: "Pushover",
+ description: "Pushover is a service for receiving real-time push notifications on your devices.",
+ iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pushover.svg",
+ path: "../../integrations/pushover",
+};
diff --git a/apps/docs/docs/widgets/notifications/index.mdx b/apps/docs/docs/widgets/notifications/index.mdx
index 0a715ab3c..1f58d8ec6 100644
--- a/apps/docs/docs/widgets/notifications/index.mdx
+++ b/apps/docs/docs/widgets/notifications/index.mdx
@@ -15,6 +15,7 @@ import { notificationsWidget } from '.';
import { ntfyIntegration } from '@site/docs/integrations/ntfy';
import { nextcloudIntegration } from '@site/docs/integrations/nextcloud';
import { gotifyIntegration } from '@site/docs/integrations/gotify';
+import { pushoverIntegration } from '@site/docs/integrations/pushover';
diff --git a/packages/definitions/src/docs/homarr-docs-sitemap.ts b/packages/definitions/src/docs/homarr-docs-sitemap.ts
index efc75332c..e150afc21 100644
--- a/packages/definitions/src/docs/homarr-docs-sitemap.ts
+++ b/packages/definitions/src/docs/homarr-docs-sitemap.ts
@@ -182,6 +182,7 @@ export type HomarrDocumentationPath =
| "/docs/integrations/glances"
| "/docs/integrations/gluetun"
| "/docs/integrations/gotify"
+ | "/docs/integrations/pushover"
| "/docs/integrations/home-assistant"
| "/docs/integrations/ical"
| "/docs/integrations/immich"
@@ -326,6 +327,7 @@ export type HomarrDocumentationPath =
| "/docs/integrations/quay"
| "/docs/integrations/ntfy"
| "/docs/integrations/gotify"
+ | "/docs/integrations/pushover"
| "/docs/integrations/ical"
| "/docs/integrations/anchor"
| "/docs/integrations/truenas"
diff --git a/packages/definitions/src/docs/integration-doc-slugs.ts b/packages/definitions/src/docs/integration-doc-slugs.ts
index 90c3cae8b..fe430fc31 100644
--- a/packages/definitions/src/docs/integration-doc-slugs.ts
+++ b/packages/definitions/src/docs/integration-doc-slugs.ts
@@ -41,6 +41,7 @@ export const integrationDocSlugs: Record = {
quay: "quay",
ntfy: "ntfy",
gotify: "gotify",
+ pushover: "pushover",
ical: "ical",
anchor: "anchor",
truenas: "truenas",
diff --git a/packages/definitions/src/integration.ts b/packages/definitions/src/integration.ts
index d6c3a2f58..df17135c4 100644
--- a/packages/definitions/src/integration.ts
+++ b/packages/definitions/src/integration.ts
@@ -18,6 +18,7 @@ export const integrationSecretKindObject = {
githubAppId: { isPublic: true, multiline: false },
githubInstallationId: { isPublic: true, multiline: false },
slug: { isPublic: true, multiline: false },
+ userKey: { isPublic: false, multiline: false },
} satisfies Record;
export const integrationSecretKinds = objectKeys(integrationSecretKindObject);
@@ -362,6 +363,14 @@ export const integrationDefs = {
category: ["notifications"],
documentationUrl: createDocumentationLink("/docs/integrations/gotify"),
},
+ pushover: {
+ name: "Pushover",
+ secretKinds: [["apiKey", "userKey"]],
+ iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/pushover.svg",
+ category: ["notifications"],
+ defaultUrl: "https://api.pushover.net",
+ documentationUrl: createDocumentationLink("/docs/integrations/pushover"),
+ },
ical: {
name: "iCal",
secretKinds: [["url"]],
diff --git a/packages/integrations/src/base/creator.ts b/packages/integrations/src/base/creator.ts
index 13ed36537..b46cfddf0 100644
--- a/packages/integrations/src/base/creator.ts
+++ b/packages/integrations/src/base/creator.ts
@@ -39,6 +39,7 @@ import { NextcloudIntegration } from "../nextcloud/nextcloud.integration";
import { NPMIntegration } from "../npm/npm-integration";
import { GotifyIntegration } from "../gotify/gotify-integration";
import { NTFYIntegration } from "../ntfy/ntfy-integration";
+import { PushoverIntegration } from "../pushover/pushover-integration";
import { OpenMediaVaultIntegration } from "../openmediavault/openmediavault-integration";
import { OPNsenseIntegration } from "../opnsense/opnsense-integration";
import { OverseerrIntegration } from "../overseerr/overseerr-integration";
@@ -125,6 +126,7 @@ export const integrationCreators = {
quay: QuayIntegration,
ntfy: NTFYIntegration,
gotify: GotifyIntegration,
+ pushover: PushoverIntegration,
mock: MockIntegration,
truenas: TrueNasIntegration,
unraid: UnraidIntegration,
diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts
index 66cf4be51..e65dca2e9 100644
--- a/packages/integrations/src/index.ts
+++ b/packages/integrations/src/index.ts
@@ -19,6 +19,7 @@ export { ReadarrIntegration } from "./media-organizer/readarr/readarr-integratio
export { SonarrIntegration } from "./media-organizer/sonarr/sonarr-integration";
export { NextcloudIntegration } from "./nextcloud/nextcloud.integration";
export { GotifyIntegration } from "./gotify/gotify-integration";
+export { PushoverIntegration } from "./pushover/pushover-integration";
export { NTFYIntegration } from "./ntfy/ntfy-integration";
export { OpenMediaVaultIntegration } from "./openmediavault/openmediavault-integration";
export { GlancesIntegration } from "./glances/glances-integration";
diff --git a/packages/integrations/src/pushover/pushover-integration.ts b/packages/integrations/src/pushover/pushover-integration.ts
new file mode 100644
index 000000000..6c17b7601
--- /dev/null
+++ b/packages/integrations/src/pushover/pushover-integration.ts
@@ -0,0 +1,38 @@
+import { ResponseError } from "@homarr/common/server";
+import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
+
+import { Integration } from "../base/integration";
+import type { IntegrationTestingInput } from "../base/integration";
+import type { TestingResult } from "../base/test-connection/test-connection-service";
+import type { Notification } from "../interfaces/notifications/notification-types";
+import type { INotificationsIntegration } from "../interfaces/notifications/notifications-integration";
+import { pushoverLimitsResponseSchema } from "./pushover-schema";
+
+export class PushoverIntegration extends Integration implements INotificationsIntegration {
+ public async testingAsync(input: IntegrationTestingInput): Promise {
+ const token = this.getSecretValue("apiKey");
+ const url = this.url("/1/apps/limits.json", { token });
+ const response = await input.fetchAsync(url);
+
+ if (!response.ok) throw new ResponseError(response);
+
+ const result = await pushoverLimitsResponseSchema.safeParseAsync(await response.json());
+ if (!result.success) throw new Error(`Failed to parse Pushover response: ${result.error.message}`);
+ if (result.data.status !== 1) throw new Error("Pushover API returned non-success status");
+
+ return { success: true };
+ }
+
+ public async getNotificationsAsync(): Promise {
+ const token = this.getSecretValue("apiKey");
+ const url = this.url("/1/apps/limits.json", { token });
+ const response = await fetchWithTrustedCertificatesAsync(url);
+
+ if (!response.ok) throw new ResponseError(response);
+
+ const result = await pushoverLimitsResponseSchema.safeParseAsync(await response.json());
+ if (!result.success) throw new Error(`Failed to parse Pushover response: ${result.error.message}`);
+
+ return [];
+ }
+}
diff --git a/packages/integrations/src/pushover/pushover-schema.ts b/packages/integrations/src/pushover/pushover-schema.ts
new file mode 100644
index 000000000..9d16453d4
--- /dev/null
+++ b/packages/integrations/src/pushover/pushover-schema.ts
@@ -0,0 +1,9 @@
+import { z } from "zod/v4";
+
+export const pushoverLimitsResponseSchema = z.object({
+ status: z.number(),
+ request: z.string(),
+ limit: z.number().optional(),
+ remaining: z.number().optional(),
+ reset: z.number().optional(),
+});