Skip to content
Sebastian edited this page Jan 2, 2026 · 29 revisions

iGotify Notification Assistent

Welcome! Here you will find guides and documentation to get started with the iGotify Notification Assistent.

What is iGotify?

iGotify is a small notification assistant that encrypts messages and triggers push notifications to iOS devices via Apple's APNs using the SecNtfy service.

It bridges Gotify - a self-hosted push notification server - with iOS devices, allowing you to receive real-time notifications securely on your iPhone or iPad.

Key Features

  • End-to-end encrypted push notifications
  • Seamless integration with Gotify
  • Native iOS app with FaceID support
  • Custom notification settings
  • App and contact images for notifications (see note below)

Note

App and contact images only work when not using a local instance, as the app cannot access internal Gotify URLs to fetch icons.

Get the App

Download on the App Store

How it works

┌─────────────┐    WebSocket     ┌──────────────────────┐    HTTPS      ┌─────────────┐   APNs      ┌─────────────┐
│   Gotify    │ ──────────────▶  │ iGotify Assistant    │ ───────────▶  │   SecNtfy   │ ─────────▶  │  iOS Device │
│   Server    │                  │ (Notification Relay) │               │   Service   │             │  (iGotify)  │
└─────────────┘                  └──────────────────────┘               └─────────────┘             └─────────────┘
  1. Gotify receives a notification (e.g., from your server, automation, or app)
  2. iGotify Assistant connects to Gotify via WebSocket and listens for new messages
  3. When a message arrives, it encrypts and forwards it to SecNtfy
  4. SecNtfy sends a push notification through Apple's APNs to your iOS device
  5. The iGotify app receives and displays the notification

Note

Privacy at SecNtfy: SecNtfy only stores anonymized data (such as device tokens). The payload (your actual notification content) is never stored - it is forwarded 1:1 to Apple's APNs and immediately discarded.

Understanding Tokens

iGotify uses multiple tokens to identify devices and connections:

Token Purpose
NTFY-Device Token Unique identifier for your physical device (iPhone/iPad). Generated when you first register a device.
Client Token Gotify client token. Generated for each Gotify instance you connect to.

These tokens are stored in the iGotify Assistant database along with the Gotify URL for the WebSocket connection.

Multiple Instances & Accounts

Adding a second Gotify account on the same device:

  • You receive a new Client Token from Gotify
  • Your NTFY-Device Token stays the same (same physical device)
  • In the database, your NTFY-Device Token appears twice - once for each Gotify client
  • You'll receive notifications from both Gotify instances on the same device

Multiple users (different devices):

  • Each device gets its own unique NTFY-Device Token
  • Each user has their own Client Token(s) for their Gotify connection(s)
  • Notifications are routed to the correct device based on the NTFY-Device Token

Local vs. External Instance

You can run iGotify in two modes:

Mode Description Pros Cons
Local Instance The iOS app communicates directly with Gotify through your local network Lower latency, no need to expose iGotify port (8681) externally App/contact images don't work (app can't access internal Gotify URLs for icons)
External Instance The iOS app communicates through the iGotify Assistant App/contact images work, accessible from anywhere Requires exposing port 8681, slightly higher latency

Tip

Use local instance if you primarily use iGotify within your home network and don't need app icons in notifications. Use external instance if you want full functionality including images, or need access from outside your network.


Installation

Docker

Prerequisites

  • Docker and Docker Compose (latest version recommended)
  • A docker-compose.yaml file

Environment Variables

Variable Description Default
GOTIFY_DEFAULTUSER_PASS Password for the default user -
GOTIFY_URLS Gotify server URLs (e.g., http://gotify or https://mygotify.domain.tld) -
GOTIFY_CLIENT_TOKENS Client tokens from Gotify (e.g., cXXXXXXXX) -
SECNTFY_TOKENS SecNtfy tokens from the app (e.g., NTFY-DEVICE-XXXXXX) -
ENABLE_CONSOLE_LOG Enable/disable console logging true
ENABLE_SCALAR_UI Enable/disable the API endpoint page true

Note

GOTIFY_URLS, GOTIFY_CLIENT_TOKENS, and SECNTFY_TOKENS are only required when Gotify and iGotify are not accessible via a public domain. If both services are available over a domain, these can be left empty.

Warning

If you set any of the three variables (GOTIFY_URLS, GOTIFY_CLIENT_TOKENS, SECNTFY_TOKENS), you must set all three. If any value is missing, the connection will fail.

Multiple Instances

You can configure multiple Gotify instances by separating values with a semicolon ;:

GOTIFY_URLS='http://gotify;http://gotify2;http://gotify3'
GOTIFY_CLIENT_TOKENS='cXXXXXXXX1;cXXXXXXXX2;cXXXXXXXX3'
SECNTFY_TOKENS='NTFY-DEVICE-XXXXXX1;NTFY-DEVICE-XXXXXX2;NTFY-DEVICE-XXXXXX3'

Tip

All configuration values are displayed in the iOS app after setup.

Environment File

Create a .env file in the same directory as your docker-compose.yaml:

# .env

# Required
GOTIFY_DEFAULTUSER_PASS=my-very-strong-password

# Connection settings (only required if NOT using a public domain)
# Examples for GOTIFY_URLS:
#   Internal: http://gotify (container name / local DNS)
#   External: https://mygotify.domain.tld
# For multiple instances, separate with semicolons
GOTIFY_URLS=
GOTIFY_CLIENT_TOKENS=
SECNTFY_TOKENS=

# Optional (write boolean values in single quotes)
ENABLE_CONSOLE_LOG='true'
ENABLE_SCALAR_UI='true'

Important

  • Change the password to a secure one before starting the stack!
  • Make sure to set proper permissions: chmod 600 .env

Docker Compose

Note

The iGotify port (8681) only needs to be exposed if you are not using a local instance in the iOS app setup. When using a local instance, the app communicates directly with Gotify and does not need external access to iGotify.

Standard
services:
  gotify:
    container_name: gotify
    hostname: gotify
    image: gotify/server          # Uncomment correct server image
    # image: gotify/server-arm7
    # image: gotify/server-arm64
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - net
    ports:
      - "8680:80"
    volumes:
      - data:/app/data
    environment:
      GOTIFY_DEFAULTUSER_PASS: ${GOTIFY_DEFAULTUSER_PASS}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s

  igotify:
    container_name: igotify
    hostname: igotify
    image: ghcr.io/androidseb25/igotify-notification-assist
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    depends_on:
      gotify:
        condition: service_healthy
    networks:
      - net
    ports:
      - "8681:8080"
    volumes:
      - api-data:/app/data
    environment:
      GOTIFY_URLS: ${GOTIFY_URLS}
      GOTIFY_CLIENT_TOKENS: ${GOTIFY_CLIENT_TOKENS}
      SECNTFY_TOKENS: ${SECNTFY_TOKENS}
      ENABLE_CONSOLE_LOG: ${ENABLE_CONSOLE_LOG}
      ENABLE_SCALAR_UI: ${ENABLE_SCALAR_UI}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/Version"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s

networks:
  net:

volumes:
  data:
  api-data:

Thanks to The_Think3r and @herrpandora

With Traefik
services:
  gotify:
    container_name: gotify
    hostname: gotify
    image: gotify/server
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - net
      - proxy
    ports:
      - "8680:80"
    volumes:
      - data:/app/data
    environment:
      GOTIFY_DEFAULTUSER_PASS: ${GOTIFY_DEFAULTUSER_PASS}
      GOTIFY_REGISTRATION: 'false'
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s
    labels:
      traefik.docker.network: proxy
      traefik.enable: "true"
      traefik.http.routers.gotify-secure.entrypoints: websecure
      traefik.http.routers.gotify-secure.middlewares: default@file
      traefik.http.routers.gotify-secure.rule: Host(`gotify.domain-name.de`)
      traefik.http.routers.gotify-secure.service: gotify
      traefik.http.routers.gotify-secure.tls: "true"
      traefik.http.routers.gotify-secure.tls.certresolver: http_resolver
      traefik.http.routers.gotify.entrypoints: web
      traefik.http.routers.gotify.rule: Host(`gotify.domain-name.de`)
      traefik.http.services.gotify.loadbalancer.server.port: "80"

  igotify:
    container_name: igotify
    hostname: igotify
    image: ghcr.io/androidseb25/igotify-notification-assist
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    depends_on:
      gotify:
        condition: service_healthy
    networks:
      - net
      - proxy
    ports:
      - "8681:8080"
    volumes:
      - api-data:/app/data
    environment:
      GOTIFY_URLS: ${GOTIFY_URLS}
      GOTIFY_CLIENT_TOKENS: ${GOTIFY_CLIENT_TOKENS}
      SECNTFY_TOKENS: ${SECNTFY_TOKENS}
      ENABLE_CONSOLE_LOG: ${ENABLE_CONSOLE_LOG}
      ENABLE_SCALAR_UI: ${ENABLE_SCALAR_UI}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/Version"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s
    labels:
      traefik.docker.network: proxy
      traefik.enable: "true"
      traefik.http.routers.igotify-secure.entrypoints: websecure
      traefik.http.routers.igotify-secure.middlewares: default@file
      traefik.http.routers.igotify-secure.rule: Host(`igotify.domain-name.de`)
      traefik.http.routers.igotify-secure.service: igotify
      traefik.http.routers.igotify-secure.tls: "true"
      traefik.http.routers.igotify-secure.tls.certresolver: http_resolver
      traefik.http.routers.igotify.entrypoints: web
      traefik.http.routers.igotify.rule: Host(`igotify.domain-name.de`)
      traefik.http.services.igotify.loadbalancer.server.port: "8080"

networks:
  net:
  proxy:
    external: true

volumes:
  data:
  api-data:

Thanks to @majo1989

Starting

docker compose up -d

Linux Service

You can run the assistant as a service on any Linux distribution using the install script.

Note

Thanks to @nicedevil007 for writing the original installation guide.

Warning

After updating your distribution, check if the iGotify service is running.

Supported Distributions

Distribution Package Manager Init System
Debian / Ubuntu apt systemd
RHEL / CentOS / Fedora dnf / yum systemd
Alpine apk OpenRC

Installation

Run the following command as root:

curl -sSL https://github.com/androidseb25/iGotify-Notification-Assistent/raw/branch/main/install.sh | bash

Or download and review the script first:

curl -sSL https://github.com/androidseb25/iGotify-Notification-Assistent/raw/branch/main/install.sh -o install.sh
chmod +x install.sh
./install.sh
View install script
#!/bin/bash
set -e

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo -e "${GREEN}=== iGotify Notification Assistent Installer ===${NC}"

# Check if running as root
if [ "$EUID" -ne 0 ]; then
  echo -e "${RED}Please run as root${NC}"
  exit 1
fi

# Detect distribution
detect_distro() {
  if [ -f /etc/os-release ]; then
    . /etc/os-release
    DISTRO=$ID
  elif [ -f /etc/alpine-release ]; then
    DISTRO="alpine"
  else
    echo -e "${RED}Unsupported distribution${NC}"
    exit 1
  fi
  echo -e "${GREEN}Detected distribution: ${DISTRO}${NC}"
}

# Detect init system
detect_init() {
  if command -v systemctl &> /dev/null && systemctl --version &> /dev/null; then
    INIT_SYSTEM="systemd"
  elif command -v rc-service &> /dev/null; then
    INIT_SYSTEM="openrc"
  else
    echo -e "${RED}Unsupported init system${NC}"
    exit 1
  fi
  echo -e "${GREEN}Detected init system: ${INIT_SYSTEM}${NC}"
}

# Install dependencies
install_deps() {
  echo -e "${YELLOW}Installing dependencies...${NC}"
  case $DISTRO in
    alpine)
      apk update
      apk add libstdc++ icu-libs curl bash unzip
      ;;
    debian|ubuntu)
      apt-get update
      apt-get install -y curl unzip libicu-dev
      ;;
    rhel|centos|fedora|rocky|almalinux)
      if command -v dnf &> /dev/null; then
        dnf install -y curl unzip libicu
      else
        yum install -y curl unzip libicu
      fi
      ;;
    *)
      echo -e "${RED}Unsupported distribution: ${DISTRO}${NC}"
      exit 1
      ;;
  esac
  echo -e "${GREEN}Dependencies installed${NC}"
}

# Install .NET
install_dotnet() {
  echo -e "${YELLOW}Installing .NET 10.0...${NC}"

  export DOTNET_ROOT=/opt/dotnet
  mkdir -p $DOTNET_ROOT

  curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -c 10.0 --install-dir $DOTNET_ROOT

  # Create symlink for system-wide access
  ln -sf $DOTNET_ROOT/dotnet /usr/local/bin/dotnet

  echo -e "${GREEN}.NET installed: $(dotnet --version)${NC}"
}

# Download and install iGotify
install_igotify() {
  echo -e "${YELLOW}Downloading iGotify...${NC}"

  RELEASE=$(curl -s https://api.github.com/repos/androidseb25/iGotify-Notification-Assistent/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }')
  echo -e "${GREEN}Latest version: v${RELEASE}${NC}"

  cd /tmp
  rm -f igotify.zip
  curl -sSL -o igotify.zip "https://github.com/androidseb25/iGotify-Notification-Assistent/releases/download/v${RELEASE}/iGotify-Notification-Service-amd64-v${RELEASE}.zip"

  # Remove old installation if exists
  [ -d /opt/iGotify ] && rm -rf /opt/iGotify

  mkdir -p /opt/iGotify
  unzip -q igotify.zip -d /opt/iGotify
  rm -f igotify.zip

  echo -e "${GREEN}iGotify installed to /opt/iGotify${NC}"
}

# Setup systemd service
setup_systemd() {
  echo -e "${YELLOW}Setting up systemd service...${NC}"

  DOTNET_PATH=$(command -v dotnet || echo "/opt/dotnet/dotnet")

  cat <<EOF >/etc/systemd/system/iGotify.service
[Unit]
Description=iGotify Notification Assistent
After=network.target

[Service]
WorkingDirectory=/opt/iGotify
ExecStart=${DOTNET_PATH} '/opt/iGotify/iGotify Notification Assist.dll'
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=igotify
Environment="ASPNETCORE_ENVIRONMENT=Production"
Environment="ASPNETCORE_URLS=http://0.0.0.0:8681"
TimeoutStopSec=30

[Install]
WantedBy=multi-user.target
EOF

  systemctl daemon-reload
  systemctl enable iGotify.service
  systemctl start iGotify.service

  echo -e "${GREEN}Systemd service created and started${NC}"
}

# Uninstall iGotify
uninstall() {
  echo -e "${YELLOW}Uninstalling iGotify...${NC}"

  detect_init

  # Stop and disable service
  if [ "$INIT_SYSTEM" = "systemd" ]; then
    systemctl stop iGotify.service 2>/dev/null || true
    systemctl disable iGotify.service 2>/dev/null || true
    rm -f /etc/systemd/system/iGotify.service
    systemctl daemon-reload
  else
    service iGotify stop 2>/dev/null || true
    rc-update del iGotify default 2>/dev/null || true
    rm -f /etc/init.d/iGotify
  fi

  # Remove iGotify
  [ -d /opt/iGotify ] && rm -rf /opt/iGotify
  echo -e "${GREEN}iGotify removed${NC}"

  # Ask about .NET
  echo ""
  read -p "Remove .NET runtime from /opt/dotnet? [y/N] " -n 1 -r
  echo
  if [[ $REPLY =~ ^[Yy]$ ]]; then
    rm -rf /opt/dotnet
    rm -f /usr/local/bin/dotnet
    echo -e "${GREEN}.NET removed${NC}"
  fi

  echo ""
  echo -e "${GREEN}=== Uninstallation complete ===${NC}"
}

# Setup OpenRC service
setup_openrc() {
  echo -e "${YELLOW}Setting up OpenRC service...${NC}"

  DOTNET_PATH=$(command -v dotnet || echo "/opt/dotnet/dotnet")

  cat <<EOF >/etc/init.d/iGotify
#!/sbin/openrc-run

export ASPNETCORE_URLS="http://0.0.0.0:8681"
export ASPNETCORE_ENVIRONMENT=Production
export DOTNET_ROOT=/opt/dotnet

name="iGotify Notification Assistent"
description="iGotify Notification Service"
supervisor="supervise-daemon"
pidfile="/var/run/iGotify.pid"
command="${DOTNET_PATH}"
command_args="'/opt/iGotify/iGotify Notification Assist.dll'"
command_user="root:root"

depend() {
  need net
}
EOF

  chmod +x /etc/init.d/iGotify
  rc-update add iGotify default
  service iGotify start

  echo -e "${GREEN}OpenRC service created and started${NC}"
}

# Main installation
install() {
  detect_distro
  detect_init
  install_deps
  install_dotnet
  install_igotify

  if [ "$INIT_SYSTEM" = "systemd" ]; then
    setup_systemd
  else
    setup_openrc
  fi

  echo ""
  echo -e "${GREEN}=== Installation complete ===${NC}"
  echo -e "iGotify is now running on port 8681"
  echo ""
  echo -e "Check status:"
  if [ "$INIT_SYSTEM" = "systemd" ]; then
    echo -e "  systemctl status iGotify"
  else
    echo -e "  service iGotify status"
  fi
}

# Parse arguments
case "${1:-}" in
  --uninstall|-u)
    uninstall
    ;;
  --help|-h)
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  --uninstall, -u  Uninstall iGotify"
    echo "  --help, -h       Show this help"
    echo ""
    echo "Without options, the script will install iGotify."
    ;;
  *)
    install
    ;;
esac

Uninstallation

To remove iGotify, run:

curl -sSL https://github.com/androidseb25/iGotify-Notification-Assistent/raw/branch/main/install.sh | bash -s -- --uninstall

Or if you have the script locally:

./install.sh --uninstall

Check Status

# Systemd
systemctl status iGotify

# OpenRC
service iGotify status

Debugging

# Check dotnet version (must be 10.x)
dotnet --version

# Run in foreground for more debug info
export ASPNETCORE_URLS="http://0.0.0.0:8681"
export ASPNETCORE_ENVIRONMENT=Production
dotnet '/opt/iGotify/iGotify Notification Assist.dll'

Reverse Proxy

If you're running iGotify behind a reverse proxy, you may need additional configuration.

NGINX Proxy Manager

If you have problems with incoming notifications, add these options under "Advanced Settings":

proxy_set_header   Host $http_host;
proxy_connect_timeout   1m;
proxy_send_timeout      1m;
proxy_read_timeout      1m;

Important

Do not check the boxes "HTTP/2 Support" and "HSTS enabled".

Thanks to @TBT-TBT


Roadmap

Status Feature Issue
📝 Planned 202X macOS Native Application #47
📝 Planned 2026 Add Message Extras support #139
📝 Planned 2026 Custom sound and vibration #141
📝 Planned 2026 Show Expanded Notification in View #163
📝 Planned 2026 Show more Messgaes via filter #191

Contributing

Want to help or found a bug?

Read the Contributing Guide.


References

YouTube

Blog Articles


Created a tutorial or review? Open an issue with the title [REFERENCE] <TITLE> and the URL.

Clone this wiki locally