diff --git a/.gitignore b/.gitignore
index a88775f3..c777cdf8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
dist/
.idea/
+site/
*.iml
diff --git a/config/config.yml b/config/config.yml
index 210df071..695d41d2 100644
--- a/config/config.yml
+++ b/config/config.yml
@@ -6,7 +6,7 @@
# listen-http: ":80"
# If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app.
-# This is optional and only required to support Android apps (which don't allow background services anymore).
+# This is optional and only required to save battery when using the Android app.
#
# firebase-key-file:
@@ -23,6 +23,8 @@
# Interval in which keepalive messages are sent to the client. This is to prevent
# intermediaries closing the connection for inactivity.
#
+# Note that the Android app has a hardcoded timeout at 77s, so it should be less than that.
+#
# keepalive-interval: 30s
# Interval in which the manager prunes old messages, deletes topics
diff --git a/docs/config.md b/docs/config.md
new file mode 100644
index 00000000..96196a5e
--- /dev/null
+++ b/docs/config.md
@@ -0,0 +1,108 @@
+# Configuring the ntfy server
+The ntfy server can be configured in three ways: using a config file (typically at `/etc/ntfy/config.yml`,
+see [config.yml](https://github.com/binwiederhier/ntfy/blob/main/config/config.yml)), via command line arguments
+or using environment variables.
+
+## Quick start
+By default, simply running `ntfy` will start the server at port 80. No configuration needed. Batteries included 😀.
+If everything works as it should, you'll see something like this:
+```
+$ ntfy
+2021/11/30 19:59:08 Listening on :80
+```
+
+You can immediately start [publishing messages](publish/index.md), or subscribe via the [Android app](subscribe/phone.md),
+[the web UI](subscribe/web.md), or simply via [curl or your favorite HTTP client](subscribe/api.md). To configure
+the server further, check out the [config options table](#config-options) or simply type `ntfy --help` to
+get a list of [command line options](#command-line-options).
+
+## Config options
+Each config options can be set in the config file `/etc/ntfy/config.yml` (e.g. `listen-http: :80`) or as a
+CLI option (e.g. `--listen-http :80`. Here's a list of all available options. Alternatively, you can set an environment
+variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
+
+| Config option | Env variable | Format | Default | Description |
+|---|---|---|---|---|
+| `listen-http` | `NTFY_LISTEN_HTTP` | `[host]:port` | `:80` | Listen address for the HTTP web server |
+| `firebase-key-file` | `NTFY_FIREBASE_KEY_FILE` | *filename* | - | If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. This is optional and only required to save battery when using the Android app. |
+| `cache-file` | `NTFY_CACHE_FILE` | *filename* | - | If set, messages are cached in a local SQLite database instead of only in-memory. This allows for service restarts without losing messages in support of the since= parameter. |
+| `cache-duration` | `NTFY_CACHE_DURATION` | *duration* | 12h | Duration for which messages will be buffered before they are deleted. This is required to support the `since=...` and `poll=1` parameter. |
+| `keepalive-interval` | `NTFY_KEEPALIVE_INTERVAL` | *duration* | 30s | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. |
+| `manager-interval` | `$NTFY_MANAGER_INTERVAL` | *duration* | 1m | Interval in which the manager prunes old messages, deletes topics and prints the stats. |
+| `global-topic-limit` | `NTFY_GLOBAL_TOPIC_LIMIT` | *number* | 5000 | Rate limiting: Total number of topics before the server rejects new topics. |
+| `visitor-subscription-limit` | `NTFY_VISITOR_SUBSCRIPTION_LIMIT` | *number* | 30 | Rate limiting: Number of subscriptions per visitor (IP address) |
+| `visitor-request-limit-burst` | `NTFY_VISITOR_REQUEST_LIMIT_BURST` | *number* | 60 | Allowed GET/PUT/POST requests per second, per visitor. This setting is the initial bucket of requests each visitor has |
+| `visitor-request-limit-replenish` | `NTFY_VISITOR_REQUEST_LIMIT_REPLENISH` | *duration* | 10s | Strongly related to `visitor-request-limit-burst`: The rate at which the bucket is refilled |
+| `behind-proxy` | `NTFY_BEHIND_PROXY` | *bool* | false | If set, the X-Forwarded-For header is used to determine the visitor IP address instead of the remote address of the connection. |
+
+The format for a *duration* is: `(smh)`, e.g. 30s, 20m or 1h.
+
+## Firebase (FCM)
+!!! info
+ Using Firebase is **optional** and only works if you modify and build your own Android .apk.
+ For a self-hosted instance, it's easier to just not bother with FCM.
+
+[Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging) is the Google approved way to send
+push messages to Android devices. FCM is the only method that an Android app can receive messages without having to run a
+[foreground service](https://developer.android.com/guide/components/foreground-services).
+
+For the main host [ntfy.sh](https://ntfy.sh), the [ntfy Android App](subscribe/phone.md) uses Firebase to send messages
+to the device. For other hosts, instant delivery is used and FCM is not involved.
+
+To configure FCM for your self-hosted instance of the ntfy server, follow these steps:
+
+1. Sign up for a [Firebase account](https://console.firebase.google.com/)
+2. Create an app and download the key file (e.g. `myapp-firebase-adminsdk-ahnce-....json`)
+3. Place the key file in `/etc/ntfy`, set the `firebase-key-file` in `config.yml` accordingly and restart the ntfy server
+4. Build your own Android .apk following [these instructions]()
+
+Example:
+```
+# If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app.
+# This is optional and only required to support Android apps (which don't allow background services anymore).
+#
+firebase-key-file: "/etc/ntfy/ntfy-sh-firebase-adminsdk-ahnce-9f4d6f14b5.json"
+```
+
+## Behind a proxy (TLS, etc.)
+
+!! warn
+If you are behind a proxy, you must set the `behind-proxy` flag. Otherwise all visitors are rate limited
+as if they are one.
+
+
+## Rate limiting
+Rate limiting: Allowed GET/PUT/POST requests per second, per visitor:
+- visitor-request-limit-burst is the initial bucket of requests each visitor has
+- visitor-request-limit-replenish is the rate at which the bucket is refilled
+
+
+## Command line options
+```
+$ ntfy --help
+NAME:
+ ntfy - Simple pub-sub notification service
+
+USAGE:
+ ntfy [OPTION..]
+
+GLOBAL OPTIONS:
+ --config value, -c value config file (default: /etc/ntfy/config.yml) [$NTFY_CONFIG_FILE]
+ --listen-http value, -l value ip:port used to as listen address (default: ":80") [$NTFY_LISTEN_HTTP]
+ --firebase-key-file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE]
+ --cache-file value, -C value cache file used for message caching [$NTFY_CACHE_FILE]
+ --cache-duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION]
+ --keepalive-interval value, -k value interval of keepalive messages (default: 30s) [$NTFY_KEEPALIVE_INTERVAL]
+ --manager-interval value, -m value interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL]
+ --global-topic-limit value, -T value total number of topics allowed (default: 5000) [$NTFY_GLOBAL_TOPIC_LIMIT]
+ --visitor-subscription-limit value, -V value number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT]
+ --visitor-request-limit-burst value, -B value initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST]
+ --visitor-request-limit-replenish value, -R value interval at which burst limit is replenished (one per x) (default: 10s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH]
+ --behind-proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY]
+
+Try 'ntfy COMMAND --help' for more information.
+
+ntfy v1.4.8 (7b8185c), runtime go1.17, built at 1637872539
+Copyright (C) 2021 Philipp C. Heckel, distributed under the Apache License 2.0
+```
+
diff --git a/docs/develop.md b/docs/develop.md
new file mode 100644
index 00000000..1d5b2a72
--- /dev/null
+++ b/docs/develop.md
@@ -0,0 +1,5 @@
+# Building
+
+## ntfy server
+
+## Android app
diff --git a/docs/examples.md b/docs/examples.md
new file mode 100644
index 00000000..df635b4e
--- /dev/null
+++ b/docs/examples.md
@@ -0,0 +1 @@
+# Examples
diff --git a/docs/faq.md b/docs/faq.md
new file mode 100644
index 00000000..947fcce6
--- /dev/null
+++ b/docs/faq.md
@@ -0,0 +1,47 @@
+# Frequently asked questions (FAQ)
+
+## Isn't this like ...?
+Who knows. I didn't do a lot of research before making this. It was fun making it.
+
+## Can I use this in my app? Will it stay free?
+Yes. As long as you don't abuse it, it'll be available and free of charge. I do not plan on monetizing
+the service.
+
+## What are the uptime guarantees?
+Best effort.
+
+## What happens if there are multiple subscribers to the same topic?
+As per usual with pub-sub, all subscribers receive notifications if they are
+subscribed to a topic.
+
+## Will you know what topics exist, can you spy on me?
+If you don't trust me or your messages are sensitive, run your own server. It's open source.
+That said, the logs do not contain any topic names or other details about you.
+Messages are cached for the duration configured in `config.yml` (12h by default) to facilitate service restarts, message polling and to overcome
+client network disruptions.
+
+## Can I self-host it?
+Yes. The server (including this Web UI) can be self-hosted, and the Android app supports adding topics from
+your own server as well. There are install instructions
+on GitHub.
+
+## Why is Firebase used?
+In addition to caching messages locally and delivering them to long-polling subscribers, all messages are also
+published to Firebase Cloud Messaging (FCM) (if `FirebaseKeyFile` is set, which it is on ntfy.sh). This
+is to facilitate instant notifications on Android.
+
+
+## How much battery does the Android app use?
+If you use the ntfy.sh server and you don't use the instant delivery feature, the Android app uses no
+additional battery, since Firebase Cloud Messaging (FCM) is used. If you use your own server, or you use
+instant delivery, the app has to maintain a constant connection to the server, which consumes about 4% of
+battery in 17h of use (on my phone). I use it and it makes no difference to me.
+
+## What is instant delivery?
+Instant delivery is a feature in the Android app. If turned on, the app maintains a constant connection to the
+server and listens for incoming notifications. This consumes additional battery,
+but delivers notifications instantly.
+
+## Why is there no iOS app (yet)?
+I don't have an iPhone or a Mac, so I didn't make an iOS app yet. It'd be awesome if
+someone else could help out.
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..bf6d425a
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,10 @@
+# ntfy.sh | simple HTTP-based pub-sub
+
+**ntfy** (pronounce: *notify*) is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)
+notification service. It allows you to send notifications to your phone or desktop via scripts from any computer,
+entirely **without signup, cost or setup**. It's also [open source](https://github.com/binwiederhier/ntfy) if you want
+to run your own.
+
+(pub sub diagram)
+
+(screenshot / video / gif)
diff --git a/docs/install.md b/docs/install.md
new file mode 100644
index 00000000..6da2e179
--- /dev/null
+++ b/docs/install.md
@@ -0,0 +1,94 @@
+# Install your own ntfy server
+The following steps are only required if you want to **self-host your own ntfy server**. If you just want to
+[send messages using ntfy.sh](publish/index.md), you don't need to install anything. Just use `curl`
+or your favorite HTTP client.
+
+## General steps
+The ntfy server comes as a statically linked binary and is shipped as tarball, deb/rpm packages and as a Docker image.
+We support amd64, armv7 and arm64.
+
+1. Install ntfy using one of the methods described below
+2. Then (optionally) edit `/etc/ntfy/config.yml` (see [configuration](config.md))
+3. Then just run it with `ntfy` (or `systemctl start ntfy` when using the deb/rpm).
+
+
+## Binaries and packages
+Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and
+deb/rpm packages.
+
+x86_64/amd64:
+```
+wget https://github.com/binwiederhier/ntfy/releases/download/v1.5.0/ntfy_1.5.0_linux_x86_64.tar.gz
+sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
+```
+
+armv7:
+```
+wget https://github.com/binwiederhier/ntfy/releases/download/v1.5.0/ntfy_1.5.0_linux_armv7.tar.gz
+sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
+```
+
+arm64/v8:
+```
+wget https://github.com/binwiederhier/ntfy/releases/download/v1.5.0/ntfy_1.5.0_linux_arm64.tar.gz
+sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
+```
+
+## Debian/Ubuntu repository
+Installation via Debian repository:
+```bash
+curl -sSL https://archive.heckel.io/apt/pubkey.txt | sudo apt-key add -
+sudo apt install apt-transport-https
+sudo sh -c "echo 'deb [arch=amd64] https://archive.heckel.io/apt debian main' > /etc/apt/sources.list.d/archive.heckel.io.list"
+sudo apt update
+sudo apt install ntfy
+```
+
+Manually installing the .deb file:
+```bash
+wget https://github.com/binwiederhier/ntfy/releases/download/v1.5.0/ntfy_1.5.0_amd64.deb
+dpkg -i ntfy_1.5.0_amd64.deb
+```
+
+## Fedora/RHEL/CentOS
+```bash
+rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.5.0/ntfy_1.5.0_amd64.rpm
+```
+
+## Docker
+The ntfy server exposes its web UI and the API on port 80, so you need to expose that in Docker. To use the persistent
+message cache, you also need to map a volume to `/var/cache/ntfy`. To change other settings, you should map `/etc/ntfy`,
+so you can edit `/etc/ntfy/config.yml`.
+
+Basic usage (no cache or additional config):
+```
+docker run -p 80:80 -it binwiederhier/ntfy
+```
+
+With persistent cache (configured as command line arguments):
+```bash
+docker run \
+ -v /var/cache/ntfy:/var/cache/ntfy \
+ -p 80:80 \
+ -it \
+ binwiederhier/ntfy \
+ --cache-file /var/cache/ntfy/cache.db
+```
+
+With other config options (configured via `/etc/ntfy/config.yml`, see [configuration](config.md) for details):
+```bash
+docker run \
+ -v /etc/ntfy:/etc/ntfy \
+ -p 80:80 \
+ -it \
+ binwiederhier/ntfy
+```
+
+## Go
+To install via Go, simply run:
+```bash
+go install heckel.io/ntfy@latest
+```
+!!! info
+ Please [let me know](https://github.com/binwiederhier/ntfy/issues) if there are any issues with this installation
+ method. The SQLite bindings require CGO and it works for me, but I have the feeling it may not work for everyone.
diff --git a/docs/publish/index.md b/docs/publish/index.md
new file mode 100644
index 00000000..986dec3c
--- /dev/null
+++ b/docs/publish/index.md
@@ -0,0 +1,207 @@
+# Publishing
+
+Publishing messages can be done via PUT or POST. Topics are created on the fly by subscribing or publishing to them.
+Because there is no sign-up, the topic is essentially a password, so pick something that's not easily guessable.
+
+Here's an example showing how to publish a simple message using a POST request:
+=== "Command line (curl)"
+ ```
+ curl -d "Backup successful 😀" ntfy.sh/mytopic
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /mytopic HTTP/1.1
+ Host: ntfy.sh
+
+ Backup successful 😀
+ ```
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/mytopic', {
+ method: 'POST', // PUT works too
+ body: 'Backup successful 😀'
+ })
+ ```
+
+=== "Go"
+ ``` go
+ http.Post("https://ntfy.sh/mytopic", "text/plain",
+ strings.NewReader("Backup successful 😀"))
+ ```
+
+=== "PHP"
+ ``` php
+ file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST', // PUT also works
+ 'header' => 'Content-Type: text/plain',
+ 'content' => 'Backup successful 😀'
+ ]
+ ]));
+ ```
+
+If you have the [Android app](../subscribe/phone.md) installed on your phone, this will create a notification that looks like this:
+
+
+
+There are more features related to publishing messages: You can set a [notification priority](#message-priority),
+a [title](#message-title), and [tag messages](#tags-emojis) 🥳 🎉. Here's an example that uses all of them at once:
+
+=== "Command line (curl)"
+ ```
+ curl \
+ -H "Title: Unauthorized access detected" \
+ -H "Priority: urgent" \
+ -H "Tags: warning,skull" \
+ -d "Remote access to phils-laptop detected. Act right away." \
+ ntfy.sh/phil_alerts
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /phil_alerts HTTP/1.1
+ Host: ntfy.sh
+ Title: Unauthorized access detected
+ Priority: urgent
+ Tags: warning,skull
+
+ Remote access to phils-laptop detected. Act right away.
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/phil_alerts', {
+ method: 'POST', // PUT works too
+ body: 'Remote access to phils-laptop detected. Act right away.',
+ headers: {
+ 'Title': 'Unauthorized access detected',
+ 'Priority': 'urgent',
+ 'Tags': 'warning,skull'
+ }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ req, _ := http.NewRequest("POST", "https://ntfy.sh/phil_alerts",
+ strings.NewReader("Remote access to phils-laptop detected. Act right away."))
+ req.Header.Set("Title", "Unauthorized access detected")
+ req.Header.Set("Priority", "urgent")
+ req.Header.Set("Tags", "warning,skull")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PHP"
+ ``` php
+ file_get_contents('https://ntfy.sh/phil_alerts', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST', // PUT also works
+ 'header' =>
+ "Content-Type: text/plain\r\n" .
+ "Title: Unauthorized access detected\r\n" .
+ "Priority: urgent\r\n" .
+ "Tags: warning,skull",
+ 'content' => 'Remote access to phils-laptop detected. Act right away.'
+ ]
+ ]));
+ ```
+
+
+
+## Message priority
+All messages have a priority, which defines how urgently your phone notifies you. You can set custom
+notification sounds and vibration patterns on your phone to map to these priorities (see [Android config](../subscribe/phone.md)).
+
+The following priorities exist:
+
+| Priority | Icon | ID | Name | Description |
+|---|---|---|---|---|
+| Max priority | ![min priority](../static/img/priority-5.svg) | `5` | `max`/`urgent` | Really long vibration bursts, default notification sound with a pop-over notification. |
+| High priority | ![min priority](../static/img/priority-4.svg) | `4` | `high` | Long vibration burst, default notification sound with a pop-over notification. |
+| **Default priority** | *(none)* | `3` | `default` | Short default vibration and sound. Default notification behavior. |
+| Low priority | ![min priority](../static/img/priority-2.svg) |`2` | `low` | No vibration or sound. Notification will not visibly show up until notification drawer is pulled down. |
+| Min priority | ![min priority](../static/img/priority-1.svg) | `1` | `min` | No vibration or sound. The notification will be under the fold in "Other notifications". |
+
+You can set the priority with the header `X-Priority` (or any of its aliases: `Priority`, `prio`, or `p`).
+
+=== "Command line (curl)"
+ ```
+ curl -H "X-Priority: 5" -d "An urgent message" ntfy.sh/phil_alerts
+ curl -H "Priority: low" -d "Low priority message" ntfy.sh/phil_alerts
+ curl -H p:4 -d "A high priority message" ntfy.sh/phil_alerts
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /phil_alerts HTTP/1.1
+ Host: ntfy.sh
+ Priority: 5
+
+ An urgent message
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/phil_alerts', {
+ method: 'POST',
+ body: 'An urgent message',
+ headers: { 'Priority': '5' }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ req, _ := http.NewRequest("POST", "https://ntfy.sh/phil_alerts", strings.NewReader("An urgent message"))
+ req.Header.Set("Priority", "5")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PHP"
+ ``` php
+ file_get_contents('https://ntfy.sh/phil_alerts', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' =>
+ "Content-Type: text/plain\r\n" .
+ "Priority: 5",
+ 'content' => 'An urgent message'
+ ]
+ ]));
+ ```
+
+
+
+## Tags & emojis 🥳 🎉
+You can tag messages with emojis (or other relevant strings). If a tag matches a known emoji short code,
+it will be converted to an emoji. If it doesn't match, it will be listed below the notification. This is useful
+for things like warnings and such (⚠️, ️🚨, or 🚩), but also to simply tag messages otherwise (e.g. which script the
+message came from, ...).
+
+You can set tags with the `X-Tags` header (or any of its aliases: `Tags`, or `ta`).
+Use this reference
+to figure out what tags can be converted to emojis. In the example below, the tag "warning" matches the emoji ⚠️,
+the tag "ssh-login" doesn't match and will be displayed below the message.
+
+```
+$ curl -H "Tags: warning,ssh-login" -d "Unauthorized SSH access" ntfy.sh/mytopic
+{"id":"ZEIwjfHlSS",...,"tags":["warning","ssh-login"],"message":"Unauthorized SSH access"}
+```
+
+## Message title
+The notification title is typically set to the topic short URL (e.g. `ntfy.sh/mytopic`.
+To override it, you can set the `X-Title` header (or any of its aliases: `Title`, `ti`, or `t`).
+
+```
+curl -H "Title: Dogs are better than cats" -d "Oh my ..." ntfy.sh/mytopic<
+```
+
diff --git a/docs/static/css/extra.css b/docs/static/css/extra.css
new file mode 100644
index 00000000..b834efce
--- /dev/null
+++ b/docs/static/css/extra.css
@@ -0,0 +1,4 @@
+figure img {
+ border-radius: 7px;
+ filter: drop-shadow(3px 3px 5px #ccc);
+}
diff --git a/docs/static/img/basic-notification.png b/docs/static/img/basic-notification.png
new file mode 100644
index 00000000..3a8a245d
Binary files /dev/null and b/docs/static/img/basic-notification.png differ
diff --git a/docs/static/img/favicon.png b/docs/static/img/favicon.png
new file mode 100644
index 00000000..92312fea
Binary files /dev/null and b/docs/static/img/favicon.png differ
diff --git a/docs/static/img/ntfy.png b/docs/static/img/ntfy.png
new file mode 100644
index 00000000..6b969a84
Binary files /dev/null and b/docs/static/img/ntfy.png differ
diff --git a/docs/static/img/priority-1.svg b/docs/static/img/priority-1.svg
new file mode 100644
index 00000000..df6a0a49
--- /dev/null
+++ b/docs/static/img/priority-1.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/docs/static/img/priority-2.svg b/docs/static/img/priority-2.svg
new file mode 100644
index 00000000..10a89ad1
--- /dev/null
+++ b/docs/static/img/priority-2.svg
@@ -0,0 +1,43 @@
+
+
diff --git a/docs/static/img/priority-4.svg b/docs/static/img/priority-4.svg
new file mode 100644
index 00000000..a1723cf8
--- /dev/null
+++ b/docs/static/img/priority-4.svg
@@ -0,0 +1,43 @@
+
+
diff --git a/docs/static/img/priority-5.svg b/docs/static/img/priority-5.svg
new file mode 100644
index 00000000..2e2c4447
--- /dev/null
+++ b/docs/static/img/priority-5.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/docs/static/img/priority-detail-overview.png b/docs/static/img/priority-detail-overview.png
new file mode 100644
index 00000000..c9321aa7
Binary files /dev/null and b/docs/static/img/priority-detail-overview.png differ
diff --git a/docs/static/img/priority-notification.png b/docs/static/img/priority-notification.png
new file mode 100644
index 00000000..31d15152
Binary files /dev/null and b/docs/static/img/priority-notification.png differ
diff --git a/docs/static/js/extra.js b/docs/static/js/extra.js
new file mode 100644
index 00000000..d588866a
--- /dev/null
+++ b/docs/static/js/extra.js
@@ -0,0 +1,31 @@
+// Link tabs, as per https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs
+
+const savedTab = localStorage.getItem('savedTab')
+const tabs = document.querySelectorAll(".tabbed-set > input")
+for (const tab of tabs) {
+ tab.addEventListener("click", () => {
+ const current = document.querySelector(`label[for=${tab.id}]`)
+ const pos = current.getBoundingClientRect().top
+ const labelContent = current.innerHTML
+ const labels = document.querySelectorAll('.tabbed-set > label, .tabbed-alternate > .tabbed-labels > label')
+ for (const label of labels) {
+ if (label.innerHTML === labelContent) {
+ document.querySelector(`input[id=${label.getAttribute('for')}]`).checked = true
+ }
+ }
+
+ // Preserve scroll position
+ const delta = (current.getBoundingClientRect().top) - pos
+ window.scrollBy(0, delta)
+
+ // Save
+ localStorage.setItem('savedTab', labelContent)
+ })
+
+ // Select saved tab
+ const current = document.querySelector(`label[for=${tab.id}]`)
+ const labelContent = current.innerHTML
+ if (savedTab === labelContent) {
+ tab.checked = true
+ }
+}
diff --git a/docs/subscribe/api.md b/docs/subscribe/api.md
new file mode 100644
index 00000000..8a77f13c
--- /dev/null
+++ b/docs/subscribe/api.md
@@ -0,0 +1 @@
+# Subscribe from your phone
diff --git a/docs/subscribe/phone.md b/docs/subscribe/phone.md
new file mode 100644
index 00000000..8a77f13c
--- /dev/null
+++ b/docs/subscribe/phone.md
@@ -0,0 +1 @@
+# Subscribe from your phone
diff --git a/docs/subscribe/poll.md b/docs/subscribe/poll.md
new file mode 100644
index 00000000..8a77f13c
--- /dev/null
+++ b/docs/subscribe/poll.md
@@ -0,0 +1 @@
+# Subscribe from your phone
diff --git a/docs/subscribe/since.md b/docs/subscribe/since.md
new file mode 100644
index 00000000..8a77f13c
--- /dev/null
+++ b/docs/subscribe/since.md
@@ -0,0 +1 @@
+# Subscribe from your phone
diff --git a/docs/subscribe/web.md b/docs/subscribe/web.md
new file mode 100644
index 00000000..7da6add2
--- /dev/null
+++ b/docs/subscribe/web.md
@@ -0,0 +1 @@
+# Subscribe from the web UI
diff --git a/examples/publish-go/main.go b/examples/publish-go/main.go
new file mode 100644
index 00000000..35fac138
--- /dev/null
+++ b/examples/publish-go/main.go
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "log"
+ "net/http"
+ "strings"
+)
+
+func main() {
+ // Without additional headers (priority, tags, title), it's a one liner.
+ // Check out https://ntfy.sh/mytopic in your browser after running this.
+ http.Post("https://ntfy.sh/mytopic", "text/plain", strings.NewReader("Backup successful 😀"))
+
+ // If you'd like to add title, priority, or tags, it's a little harder.
+ // Check out https://ntfy.sh/phil_alerts in your browser.
+ req, err := http.NewRequest("POST", "https://ntfy.sh/phil_alerts",
+ strings.NewReader("Remote access to phils-laptop detected. Act right away."))
+ if err != nil {
+ log.Fatal(err)
+ }
+ req.Header.Set("Title", "Unauthorized access detected")
+ req.Header.Set("Priority", "urgent")
+ req.Header.Set("Tags", "warning,skull")
+ if _, err := http.DefaultClient.Do(req); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/examples/publish-php/publish.php b/examples/publish-php/publish.php
new file mode 100644
index 00000000..c7c6eefc
--- /dev/null
+++ b/examples/publish-php/publish.php
@@ -0,0 +1,14 @@
+ [
+ 'method' => 'POST', // PUT also works
+ 'header' =>
+ "Content-Type: text/plain\r\n" .
+ "Title: Unauthorized access detected\r\n" .
+ "Priority: urgent\r\n" .
+ "Tags: warning,skull",
+ 'content' => 'Remote access to phils-laptop detected. Act right away.'
+ ]
+]));
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 00000000..a877bfb4
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,85 @@
+site_name: ntfy.sh
+site_url: https://ntfy.sh
+site_description: simple HTTP-based pub-sub
+copyright: Made with ❤️ by Philipp C. Heckel
+repo_name: binwiederhier/ntfy
+repo_url: https://github.com/binwiederhier/ntfy
+edit_uri: edit/main/docs/
+
+theme:
+ name: material
+# custom_dir: docs/overrides
+ language: en
+ logo: static/img/ntfy.png
+ favicon: static/img/favicon.png
+ include_search_page: false
+ search_index_only: true
+ palette:
+ - media: "(prefers-color-scheme: light)" # Light mode
+ scheme: default
+ primary: teal
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to light mode
+ - media: "(prefers-color-scheme: dark)" # Dark mode
+ scheme: slate
+ primary: teal
+ accent: indigo
+ toggle:
+ icon: material/lightbulb
+ name: Switch to dark mode
+ features:
+ - search.suggest
+ - search.highlight
+ - search.share
+ - navigation.sections
+ - toc.integrate
+ - content.tabs.link
+extra_javascript:
+ - static/js/extra.js
+extra_css:
+ - static/css/extra.css
+
+markdown_extensions:
+ - admonition
+ - codehilite
+ - meta
+ - toc:
+ permalink: true
+ - pymdownx.tabbed:
+ alternate_style: true
+ - pymdownx.superfences
+ - pymdownx.highlight
+ - pymdownx.tasklist:
+ custom_checkbox: true
+ - footnotes
+ - attr_list
+ - md_in_html
+
+plugins:
+ - search
+
+extra:
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/binwiederhier
+
+nav:
+- "Getting started": index.md
+- "Installation": install.md
+- "Configuration": config.md
+- "Publishing":
+ - "Sending messages": publish/index.md
+- "Subscribing":
+ - "From the Android/iOS app": subscribe/phone.md
+ - "From the Web UI": subscribe/web.md
+ - "Using the API":
+ - "Basic API usage": subscribe/api.md
+ - "Fetching cached messages": subscribe/since.md
+ - "Polling": subscribe/poll.md
+- "Other things":
+ - "Examples": examples.md
+ - "FAQs": faq.md
+ - "Development": develop.md
+
+