From df372d1a7ee060b0b569bebc0ccb446ace1ee69b Mon Sep 17 00:00:00 2001
From: Joe Harrison <53116754+Joeharrison94@users.noreply.github.com>
Date: Fri, 18 Mar 2022 09:17:19 +0000
Subject: [PATCH] Update to publish.md to add PowerShell examples
---
docs/publish.md | 1164 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 1159 insertions(+), 5 deletions(-)
diff --git a/docs/publish.md b/docs/publish.md
index 22329cd..2518b95 100644
--- a/docs/publish.md
+++ b/docs/publish.md
@@ -1,6 +1,7 @@
# Publishing
-Publishing messages can be done via HTTP 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.
+Publishing messages can be done via HTTP PUT/POST or via the [ntfy CLI](install.md). 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:
@@ -9,6 +10,11 @@ Here's an example showing how to publish a simple message using a POST request:
curl -d "Backup successful 😀" ntfy.sh/mytopic
```
+=== "ntfy CLI"
+ ```
+ ntfy publish mytopic "Backup successful 😀"
+ ```
+
=== "HTTP"
``` http
POST /mytopic HTTP/1.1
@@ -30,6 +36,17 @@ Here's an example showing how to publish a simple message using a POST request:
strings.NewReader("Backup successful 😀"))
```
+=== "PowerShell"
+ ``` powershell
+ Invoke-RestMethod -Method 'Post' -Uri https://ntfy.sh/topic -Body "Backup Successful 😀" -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/mytopic",
+ data="Backup successful 😀".encode(encoding='utf-8'))
+ ```
+
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
@@ -44,12 +61,12 @@ Here's an example showing how to publish a simple message using a POST request:
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:
+a [title](#message-title), and [tag messages](#tags-emojis) 🥳 🎉. Here's an example that uses some of them at together:
=== "Command line (curl)"
```
@@ -61,6 +78,16 @@ a [title](#message-title), and [tag messages](#tags-emojis) 🥳 🎉. Here's an
ntfy.sh/phil_alerts
```
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ --title "Unauthorized access detected" \
+ --tags warning,skull \
+ --priority urgent \
+ mytopic \
+ "Remote access to phils-laptop detected. Act right away."
+ ```
+
=== "HTTP"
``` http
POST /phil_alerts HTTP/1.1
@@ -95,6 +122,27 @@ a [title](#message-title), and [tag messages](#tags-emojis) 🥳 🎉. Here's an
http.DefaultClient.Do(req)
```
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/phil_alerts"
+ $headers = @{ Title="Unauthorized access detected"
+ Priority="Urgent"
+ Tags="warning,skull" }
+ $body = "Remote access to phils-laptop detected. Act right away."
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/phil_alerts",
+ data="Remote access to phils-laptop detected. Act right away.",
+ headers={
+ "Title": "Unauthorized access detected",
+ "Priority": "urgent",
+ "Tags": "warning,skull"
+ })
+ ```
+
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.sh/phil_alerts', false, stream_context_create([
@@ -126,6 +174,13 @@ you can set the `X-Title` header (or any of its aliases: `Title`, `ti`, or `t`).
curl -H "t: Dogs are better than cats" -d "Oh my ..." ntfy.sh/controversial
```
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ -t "Dogs are better than cats" \
+ controversial "Oh my ..."
+ ```
+
=== "HTTP"
``` http
POST /controversial HTTP/1.1
@@ -151,6 +206,21 @@ you can set the `X-Title` header (or any of its aliases: `Title`, `ti`, or `t`).
http.DefaultClient.Do(req)
```
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/controversial"
+ $headers = @{ Title="Dogs are better than cats" }
+ $body = "Oh my ..."
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/controversial",
+ data="Oh my ...",
+ headers={ "Title": "Dogs are better than cats" })
+ ```
+
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.sh/controversial', false, stream_context_create([
@@ -192,6 +262,13 @@ You can set the priority with the header `X-Priority` (or any of its aliases: `P
curl -H p:4 -d "A high priority message" ntfy.sh/phil_alerts
```
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ -p 5 \
+ phil_alerts An urgent message
+ ```
+
=== "HTTP"
``` http
POST /phil_alerts HTTP/1.1
@@ -217,6 +294,21 @@ You can set the priority with the header `X-Priority` (or any of its aliases: `P
http.DefaultClient.Do(req)
```
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/phil_alerts"
+ $headers = @{ Priority="Urgent" }
+ $body = "An urgent message"
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/phil_alerts",
+ data="An urgent message",
+ headers={ "Priority": "5" })
+ ```
+
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.sh/phil_alerts', false, stream_context_create([
@@ -279,7 +371,7 @@ Here's an **excerpt of emojis** I've found very useful in alert messages:
-You can set tags with the `X-Tags` header (or any of its aliases: `Tags`, or `ta`). Specify multiple tags by separating
+You can set tags with the `X-Tags` header (or any of its aliases: `Tags`, `tag`, or `ta`). Specify multiple tags by separating
them with a comma, e.g. `tag1,tag2,tag3`.
=== "Command line (curl)"
@@ -289,6 +381,13 @@ them with a comma, e.g. `tag1,tag2,tag3`.
curl -H ta:dog -d "Dogs are awesome" ntfy.sh/backups
```
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ --tags=warning,mailsrv13,daily-backup \
+ backups "Backup of mailsrv13 failed"
+ ```
+
=== "HTTP"
``` http
POST /backups HTTP/1.1
@@ -314,6 +413,21 @@ them with a comma, e.g. `tag1,tag2,tag3`.
http.DefaultClient.Do(req)
```
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/backups"
+ $headers = @{ Tags="warning,mailsrv13,daily-backup" }
+ $body = "Backup of mailsrv13 failed"
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/backups",
+ data="Backup of mailsrv13 failed",
+ headers={ "Tags": "warning,mailsrv13,daily-backup" })
+ ```
+
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.sh/backups', false, stream_context_create([
@@ -332,3 +446,1043 @@ them with a comma, e.g. `tag1,tag2,tag3`.
Detail view of notifications with tags
+## Scheduled delivery
+You can delay the delivery of messages and let ntfy send them at a later date. This can be used to send yourself
+reminders or even to execute commands at a later date (if your subscriber acts on messages).
+
+Usage is pretty straight forward. You can set the delivery time using the `X-Delay` header (or any of its aliases: `Delay`,
+`X-At`, `At`, `X-In` or `In`), either by specifying a Unix timestamp (e.g. `1639194738`), a duration (e.g. `30m`,
+`3h`, `2 days`), or a natural language time string (e.g. `10am`, `8:30pm`, `tomorrow, 3pm`, `Tuesday, 7am`,
+[and more](https://github.com/olebedev/when)).
+
+As of today, the minimum delay you can set is **10 seconds** and the maximum delay is **3 days**. This can currently
+not be configured otherwise ([let me know](https://github.com/binwiederhier/ntfy/issues) if you'd like to change
+these limits).
+
+For the purposes of [message caching](config.md#message-cache), scheduled messages are kept in the cache until 12 hours
+after they were delivered (or whatever the server-side cache duration is set to). For instance, if a message is scheduled
+to be delivered in 3 days, it'll remain in the cache for 3 days and 12 hours. Also note that naturally,
+[turning off server-side caching](#message-caching) is not possible in combination with this feature.
+
+=== "Command line (curl)"
+ ```
+ curl -H "At: tomorrow, 10am" -d "Good morning" ntfy.sh/hello
+ curl -H "In: 30min" -d "It's 30 minutes later now" ntfy.sh/reminder
+ curl -H "Delay: 1639194738" -d "Unix timestamps are awesome" ntfy.sh/itsaunixsystem
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ --at="tomorrow, 10am" \
+ hello "Good morning"
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /hello HTTP/1.1
+ Host: ntfy.sh
+ At: tomorrow, 10am
+
+ Good morning
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/hello', {
+ method: 'POST',
+ body: 'Good morning',
+ headers: { 'At': 'tomorrow, 10am' }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ req, _ := http.NewRequest("POST", "https://ntfy.sh/hello", strings.NewReader("Good morning"))
+ req.Header.Set("At", "tomorrow, 10am")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/hello"
+ $headers = @{ At="tomorrow, 10am" }
+ $body = "Good morning"
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/hello",
+ data="Good morning",
+ headers={ "At": "tomorrow, 10am" })
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/backups', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' =>
+ "Content-Type: text/plain\r\n" .
+ "At: tomorrow, 10am",
+ 'content' => 'Good morning'
+ ]
+ ]));
+ ```
+
+Here are a few examples (assuming today's date is **12/10/2021, 9am, Eastern Time Zone**):
+
+
+
+
Delay/At/In header
Message will be delivered at
Explanation
+
30m
12/10/2021, 9:30am
30 minutes from now
+
2 hours
12/10/2021, 11:30am
2 hours from now
+
1 day
12/11/2021, 9am
24 hours from now
+
10am
12/10/2021, 10am
Today at 10am (same day, because it's only 9am)
+
8am
12/11/2021, 8am
Tomorrow at 8am (because it's 9am already)
+
1639152000
12/10/2021, 11am (EST)
Today at 11am (EST)
+
+
+
+
+## Webhooks (publish via GET)
+In addition to using PUT/POST, you can also send to topics via simple HTTP GET requests. This makes it easy to use
+a ntfy topic as a [webhook](https://en.wikipedia.org/wiki/Webhook), or if your client has limited HTTP support (e.g.
+like the [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid) Android app).
+
+To send messages via HTTP GET, simply call the `/publish` endpoint (or its aliases `/send` and `/trigger`). Without
+any arguments, this will send the message `triggered` to the topic. However, you can provide all arguments that are
+also supported as HTTP headers as URL-encoded arguments. Be sure to check the list of all
+[supported parameters and headers](#list-of-all-parameters) for details.
+
+For instance, assuming your topic is `mywebhook`, you can simply call `/mywebhook/trigger` to send a message
+(aka trigger the webhook):
+
+=== "Command line (curl)"
+ ```
+ curl ntfy.sh/mywebhook/trigger
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy trigger mywebhook
+ ```
+
+=== "HTTP"
+ ``` http
+ GET /mywebhook/trigger HTTP/1.1
+ Host: ntfy.sh
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/mywebhook/trigger')
+ ```
+
+=== "Go"
+ ``` go
+ http.Get("https://ntfy.sh/mywebhook/trigger")
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ Invoke-RestMethod -Method 'Get' -Uri "ntfy.sh/mywebhook/trigger"
+ ```
+
+=== "Python"
+ ``` python
+ requests.get("https://ntfy.sh/mywebhook/trigger")
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/mywebhook/trigger');
+ ```
+
+To add a custom message, simply append the `message=` URL parameter. And of course you can set the
+[message priority](#message-priority), the [message title](#message-title), and [tags](#tags-emojis) as well.
+For a full list of possible parameters, check the list of [supported parameters and headers](#list-of-all-parameters).
+
+Here's an example with a custom message, tags and a priority:
+
+=== "Command line (curl)"
+ ```
+ curl "ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull"
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ -p 5 --tags=warning,skull \
+ mywebhook "Webhook triggered"
+ ```
+
+=== "HTTP"
+ ``` http
+ GET /mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull HTTP/1.1
+ Host: ntfy.sh
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull')
+ ```
+
+=== "Go"
+ ``` go
+ http.Get("https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull")
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ Invoke-RestMethod -Method 'Get' -Uri "ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull"
+ ```
+
+=== "Python"
+ ``` python
+ requests.get("https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull")
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull');
+ ```
+
+## Publish as JSON
+For some integrations with other tools (e.g. [Jellyfin](https://jellyfin.org/), [overseerr](https://overseerr.dev/)),
+adding custom headers to HTTP requests may be tricky or impossible, so ntfy also allows publishing the entire message
+as JSON in the request body.
+
+To publish as JSON, simple PUT/POST the JSON object directly to the ntfy root URL. The message format is described below
+the example.
+
+!!! info
+ To publish as JSON, you must **PUT/POST to the ntfy root URL**, not to the topic URL. Be sure to check that you're
+ POST-ing to `https://ntfy.sh/` (correct), and not to `https://ntfy.sh/mytopic` (incorrect).
+
+Here's an example using all supported parameters. The `topic` parameter is the only required one:
+
+=== "Command line (curl)"
+ ```
+ curl ntfy.sh \
+ -d '{
+ "topic": "mytopic",
+ "message": "Disk space is low at 5.1 GB",
+ "title": "Low disk space alert",
+ "tags": ["warning","cd"],
+ "priority": 4,
+ "attach": "https://filesrv.lan/space.jpg",
+ "filename": "diskspace.jpg",
+ "click": "https://homecamera.lan/xasds1h2xsSsa/"
+ }'
+ ```
+
+=== "HTTP"
+ ``` http
+ POST / HTTP/1.1
+ Host: ntfy.sh
+
+ {
+ "topic": "mytopic",
+ "message": "Disk space is low at 5.1 GB",
+ "title": "Low disk space alert",
+ "tags": ["warning","cd"],
+ "priority": 4,
+ "attach": "https://filesrv.lan/space.jpg",
+ "filename": "diskspace.jpg",
+ "click": "https://homecamera.lan/xasds1h2xsSsa/"
+ }
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh', {
+ method: 'POST',
+ body: JSON.stringify({
+ "topic": "mytopic",
+ "message": "Disk space is low at 5.1 GB",
+ "title": "Low disk space alert",
+ "tags": ["warning","cd"],
+ "priority": 4,
+ "attach": "https://filesrv.lan/space.jpg",
+ "filename": "diskspace.jpg",
+ "click": "https://homecamera.lan/xasds1h2xsSsa/"
+ })
+ })
+ ```
+
+=== "Go"
+ ``` go
+ // You should probably use json.Marshal() instead and make a proper struct,
+ // or even just use req.Header.Set() like in the other examples, but for the
+ // sake of the example, this is easier.
+
+ body := `{
+ "topic": "mytopic",
+ "message": "Disk space is low at 5.1 GB",
+ "title": "Low disk space alert",
+ "tags": ["warning","cd"],
+ "priority": 4,
+ "attach": "https://filesrv.lan/space.jpg",
+ "filename": "diskspace.jpg",
+ "click": "https://homecamera.lan/xasds1h2xsSsa/"
+ }`
+ req, _ := http.NewRequest("POST", "https://ntfy.sh/", strings.NewReader(body))
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh"
+ $body = @{
+ "topic"="powershell"
+ "title"="Low disk space alert"
+ "message"="Disk space is low at 5.1 GB"
+ "priority"=4
+ "attach"="https://filesrv.lan/space.jpg"
+ "filename"="diskspace.jpg"
+ "tags"=@("warning","cd")
+ "click"= "https://homecamera.lan/xasds1h2xsSsa/"
+ } | ConvertTo-Json
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Body $body -ContentType "application/json" -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/",
+ data=json.dumps({
+ "topic": "mytopic",
+ "message": "Disk space is low at 5.1 GB",
+ "title": "Low disk space alert",
+ "tags": ["warning","cd"],
+ "priority": 4,
+ "attach": "https://filesrv.lan/space.jpg",
+ "filename": "diskspace.jpg",
+ "click": "https://homecamera.lan/xasds1h2xsSsa/"
+ })
+ )
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' => "Content-Type: application/json",
+ 'content' => json_encode([
+ "topic": "mytopic",
+ "message": "Disk space is low at 5.1 GB",
+ "title": "Low disk space alert",
+ "tags": ["warning","cd"],
+ "priority": 4,
+ "attach": "https://filesrv.lan/space.jpg",
+ "filename": "diskspace.jpg",
+ "click": "https://homecamera.lan/xasds1h2xsSsa/"
+ ])
+ ]
+ ]));
+ ```
+
+The JSON message format closely mirrors the format of the message you can consume when you [subscribe via the API](subscribe/api.md)
+(see [JSON message format](subscribe/api.md#json-message-format) for details), but is not exactly identical. Here's an overview of
+all the supported fields:
+
+| Field | Required | Type | Example | Description |
+|------------|----------|----------------------------------|--------------------------------|-----------------------------------------------------------------------|
+| `topic` | ✔️ | *string* | `topic1` | Target topic name |
+| `message` | - | *string* | `Some message` | Message body; set to `triggered` if empty or not passed |
+| `title` | - | *string* | `Some title` | Message [title](#message-title) |
+| `tags` | - | *string array* | `["tag1","tag2"]` | List of [tags](#tags-emojis) that may or not map to emojis |
+| `priority` | - | *int (one of: 1, 2, 3, 4, or 5)* | `4` | Message [priority](#message-priority) with 1=min, 3=default and 5=max |
+| `click` | - | *URL* | `https://example.com` | Website opened when notification is [clicked](#click-action) |
+| `attach` | - | *URL* | `https://example.com/file.jpg` | URL of an attachment, see [attach via URL](#attach-file-from-url) |
+| `filename` | - | *string* | `file.jpg` | File name of the attachment |
+
+
+## Click action
+You can define which URL to open when a notification is clicked. This may be useful if your notification is related
+to a Zabbix alert or a transaction that you'd like to provide the deep-link for. Tapping the notification will open
+the web browser (or the app) and open the website.
+
+Here's an example that will open Reddit when the notification is clicked:
+
+=== "Command line (curl)"
+ ```
+ curl \
+ -d "New messages on Reddit" \
+ -H "Click: https://www.reddit.com/message/messages" \
+ ntfy.sh/reddit_alerts
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ --click="https://www.reddit.com/message/messages" \
+ reddit_alerts "New messages on Reddit"
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /reddit_alerts HTTP/1.1
+ Host: ntfy.sh
+ Click: https://www.reddit.com/message/messages
+
+ New messages on Reddit
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/reddit_alerts', {
+ method: 'POST',
+ body: 'New messages on Reddit',
+ headers: { 'Click': 'https://www.reddit.com/message/messages' }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ req, _ := http.NewRequest("POST", "https://ntfy.sh/reddit_alerts", strings.NewReader("New messages on Reddit"))
+ req.Header.Set("Click", "https://www.reddit.com/message/messages")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/reddit_alerts"
+ $headers = @{ Click="https://www.reddit.com/message/messages" }
+ $body = "New messages on Reddit"
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/reddit_alerts",
+ data="New messages on Reddit",
+ headers={ "Click": "https://www.reddit.com/message/messages" })
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/reddit_alerts', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' =>
+ "Content-Type: text/plain\r\n" .
+ "Click: https://www.reddit.com/message/messages",
+ 'content' => 'New messages on Reddit'
+ ]
+ ]));
+ ```
+
+## Attachments
+You can **send images and other files to your phone** as attachments to a notification. The attachments are then downloaded
+onto your phone (depending on size and setting automatically), and can be used from the Downloads folder.
+
+There are two different ways to send attachments:
+
+* sending [a local file](#attach-local-file) via PUT, e.g. from `~/Flowers/flower.jpg` or `ringtone.mp3`
+* or by [passing an external URL](#attach-file-from-a-url) as an attachment, e.g. `https://f-droid.org/F-Droid.apk`
+
+### Attach local file
+To **send a file from your computer** as an attachment, you can send it as the PUT request body. If a message is greater
+than the maximum message size (4,096 bytes) or consists of non UTF-8 characters, the ntfy server will automatically
+detect the mime type and size, and send the message as an attachment file. To send smaller text-only messages or files
+as attachments, you must pass a filename by passing the `X-Filename` header or query parameter (or any of its aliases
+`Filename`, `File` or `f`).
+
+By default, and how ntfy.sh is configured, the **max attachment size is 15 MB** (with 100 MB total per visitor).
+Attachments **expire after 3 hours**, which typically is plenty of time for the user to download it, or for the Android app
+to auto-download it. Please also check out the [other limits below](#limitations).
+
+Here's an example showing how to upload an image:
+
+=== "Command line (curl)"
+ ```
+ curl \
+ -T flower.jpg \
+ -H "Filename: flower.jpg" \
+ ntfy.sh/flowers
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ --file=flower.jpg \
+ flowers
+ ```
+
+=== "HTTP"
+ ``` http
+ PUT /flowers HTTP/1.1
+ Host: ntfy.sh
+ Filename: flower.jpg
+ Content-Type: 52312
+
+
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/flowers', {
+ method: 'PUT',
+ body: document.getElementById("file").files[0],
+ headers: { 'Filename': 'flower.jpg' }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ file, _ := os.Open("flower.jpg")
+ req, _ := http.NewRequest("PUT", "https://ntfy.sh/flowers", file)
+ req.Header.Set("Filename", "flower.jpg")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "Python"
+ ``` python
+ requests.put("https://ntfy.sh/flowers",
+ data=open("flower.jpg", 'rb'),
+ headers={ "Filename": "flower.jpg" })
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/flowers', false, stream_context_create([
+ 'http' => [
+ 'method' => 'PUT',
+ 'header' =>
+ "Content-Type: application/octet-stream\r\n" . // Does not matter
+ "Filename: flower.jpg",
+ 'content' => file_get_contents('flower.jpg') // Dangerous for large files
+ ]
+ ]));
+ ```
+
+Here's what that looks like on Android:
+
+
+
+### Attach file from a URL
+Instead of sending a local file to your phone, you can use **an external URL** to specify where the attachment is hosted.
+This could be a Dropbox link, a file from social media, or any other publicly available URL. Since the files are
+externally hosted, the expiration or size limits from above do not apply here.
+
+To attach an external file, simple pass the `X-Attach` header or query parameter (or any of its aliases `Attach` or `a`)
+to specify the attachment URL. It can be any type of file.
+
+ntfy will automatically try to derive the file name from the URL (e.g `https://example.com/flower.jpg` will yield a
+filename `flower.jpg`). To override this filename, you may send the `X-Filename` header or query parameter (or any of its
+aliases `Filename`, `File` or `f`).
+
+Here's an example showing how to attach an APK file:
+
+=== "Command line (curl)"
+ ```
+ curl \
+ -X POST \
+ -H "Attach: https://f-droid.org/F-Droid.apk" \
+ ntfy.sh/mydownloads
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ --attach="https://f-droid.org/F-Droid.apk" \
+ mydownloads
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /mydownloads HTTP/1.1
+ Host: ntfy.sh
+ Attach: https://f-droid.org/F-Droid.apk
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/mydownloads', {
+ method: 'POST',
+ headers: { 'Attach': 'https://f-droid.org/F-Droid.apk' }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ req, _ := http.NewRequest("POST", "https://ntfy.sh/mydownloads", file)
+ req.Header.Set("Attach", "https://f-droid.org/F-Droid.apk")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/mydownloads"
+ $headers = @{ Attach="https://f-droid.org/F-Droid.apk" }
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.put("https://ntfy.sh/mydownloads",
+ headers={ "Attach": "https://f-droid.org/F-Droid.apk" })
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/mydownloads', false, stream_context_create([
+ 'http' => [
+ 'method' => 'PUT',
+ 'header' =>
+ "Content-Type: text/plain\r\n" . // Does not matter
+ "Attach: https://f-droid.org/F-Droid.apk",
+ ]
+ ]));
+ ```
+
+
+
+## E-mail notifications
+You can forward messages to e-mail by specifying an address in the header. This can be useful for messages that
+you'd like to persist longer, or to blast-notify yourself on all possible channels.
+
+Usage is easy: Simply pass the `X-Email` header (or any of its aliases: `X-E-mail`, `Email`, `E-mail`, `Mail`, or `e`).
+Only one e-mail address is supported.
+
+Since ntfy does not provide auth (yet), the rate limiting is pretty strict (see [limitations](#limitations)). In the
+default configuration, you get **16 e-mails per visitor** (IP address) and then after that one per hour. On top of
+that, your IP address appears in the e-mail body. This is to prevent abuse.
+
+=== "Command line (curl)"
+ ```
+ curl \
+ -H "Email: phil@example.com" \
+ -H "Tags: warning,skull,backup-host,ssh-login" \
+ -H "Priority: high" \
+ -d "Unknown login from 5.31.23.83 to backups.example.com" \
+ ntfy.sh/alerts
+ curl -H "Email: phil@example.com" -d "You've Got Mail"
+ curl -d "You've Got Mail" "ntfy.sh/alerts?email=phil@example.com"
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ --email=phil@example.com \
+ --tags=warning,skull,backup-host,ssh-login \
+ --priority=high \
+ alerts "Unknown login from 5.31.23.83 to backups.example.com"
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /alerts HTTP/1.1
+ Host: ntfy.sh
+ Email: phil@example.com
+ Tags: warning,skull,backup-host,ssh-login
+ Priority: high
+
+ Unknown login from 5.31.23.83 to backups.example.com
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/alerts', {
+ method: 'POST',
+ body: "Unknown login from 5.31.23.83 to backups.example.com",
+ headers: {
+ 'Email': 'phil@example.com',
+ 'Tags': 'warning,skull,backup-host,ssh-login',
+ 'Priority': 'high'
+ }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ req, _ := http.NewRequest("POST", "https://ntfy.sh/alerts",
+ strings.NewReader("Unknown login from 5.31.23.83 to backups.example.com"))
+ req.Header.Set("Email", "phil@example.com")
+ req.Header.Set("Tags", "warning,skull,backup-host,ssh-login")
+ req.Header.Set("Priority", "high")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/alerts"
+ $headers = @{ Title"="Low disk space alert"
+ Priority=4
+ Tags="warning,skull,backup-host,ssh-login")
+ Email="phil@example.com" }
+ $body = "Unknown login from 5.31.23.83 to backups.example.com"
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Body $body -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/alerts",
+ data="Unknown login from 5.31.23.83 to backups.example.com",
+ headers={
+ "Email": "phil@example.com",
+ "Tags": "warning,skull,backup-host,ssh-login",
+ "Priority": "high"
+ })
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/alerts', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' =>
+ "Content-Type: text/plain\r\n" .
+ "Email: phil@example.com\r\n" .
+ "Tags: warning,skull,backup-host,ssh-login\r\n" .
+ "Priority: high",
+ 'content' => 'Unknown login from 5.31.23.83 to backups.example.com'
+ ]
+ ]));
+ ```
+
+Here's what that looks like in Google Mail:
+
+
+
+## E-mail publishing
+You can publish messages to a topic via e-mail, i.e. by sending an email to a specific address. For instance, you can
+publish a message to the topic `sometopic` by sending an e-mail to `ntfy-sometopic@ntfy.sh`. This is useful for e-mail
+based integrations such as for statuspage.io (though these days most services also support webhooks and HTTP calls).
+
+Depending on the [server configuration](config.md#e-mail-publishing), the e-mail address format can have a prefix to
+prevent spam on topics. For ntfy.sh, the prefix is configured to `ntfy-`, meaning that the general e-mail address
+format is:
+
+```
+ntfy-$topic@ntfy.sh
+```
+
+As of today, e-mail publishing only supports adding a [message title](#message-title) (the e-mail subject). Tags, priority,
+delay and other features are not supported (yet). Here's an example that will publish a message with the
+title `You've Got Mail` to topic `sometopic` (see [ntfy.sh/sometopic](https://ntfy.sh/sometopic)):
+
+
+
+## Advanced features
+
+### Authentication
+Depending on whether the server is configured to support [access control](config.md#access-control), some topics
+may be read/write protected so that only users with the correct credentials can subscribe or publish to them.
+To publish/subscribe to protected topics, you can use [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication)
+with a valid username/password. For your self-hosted server, **be sure to use HTTPS to avoid eavesdropping** and exposing
+your password.
+
+Here's a simple example:
+
+=== "Command line (curl)"
+ ```
+ curl \
+ -u phil:mypass \
+ -d "Look ma, with auth" \
+ https://ntfy.example.com/mysecrets
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ -u phil:mypass \
+ ntfy.example.com/mysecrets \
+ "Look ma, with auth"
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /mysecrets HTTP/1.1
+ Host: ntfy.example.com
+ Authorization: Basic cGhpbDpteXBhc3M=
+
+ Look ma, with auth
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.example.com/mysecrets', {
+ method: 'POST', // PUT works too
+ body: 'Look ma, with auth',
+ headers: {
+ 'Authorization': 'Basic cGhpbDpteXBhc3M='
+ }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ req, _ := http.NewRequest("POST", "https://ntfy.example.com/mysecrets",
+ strings.NewReader("Look ma, with auth"))
+ req.Header.Set("Authorization", "Basic cGhpbDpteXBhc3M=")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.example.com/mysecrets"
+ $basicAuthValue = "Basic [user:pass-bese64encoded]"
+ $headers = @{ Authorization=$basicAuthValue }
+ $body = "Look ma, with auth"
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Body $body -Headers $headers -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.example.com/mysecrets",
+ data="Look ma, with auth",
+ headers={
+ "Authorization": "Basic cGhpbDpteXBhc3M="
+ })
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.example.com/mysecrets', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST', // PUT also works
+ 'header' =>
+ 'Content-Type: text/plain\r\n' .
+ 'Authorization: Basic cGhpbDpteXBhc3M=',
+ 'content' => 'Look ma, with auth'
+ ]
+ ]));
+ ```
+
+### Message caching
+!!! info
+ If `Cache: no` is used, messages will only be delivered to connected subscribers, and won't be re-delivered if a
+ client re-connects. If a subscriber has (temporary) network issues or is reconnecting momentarily,
+ **messages might be missed**.
+
+By default, the ntfy server caches messages on disk for 12 hours (see [message caching](config.md#message-cache)), so
+all messages you publish are stored server-side for a little while. The reason for this is to overcome temporary
+client-side network disruptions, but arguably this feature also may raise privacy concerns.
+
+To avoid messages being cached server-side entirely, you can set `X-Cache` header (or its alias: `Cache`) to `no`.
+This will make sure that your message is not cached on the server, even if server-side caching is enabled. Messages
+are still delivered to connected subscribers, but [`since=`](subscribe/api.md#fetch-cached-messages) and
+[`poll=1`](subscribe/api.md#poll-for-messages) won't return the message anymore.
+
+=== "Command line (curl)"
+ ```
+ curl -H "X-Cache: no" -d "This message won't be stored server-side" ntfy.sh/mytopic
+ curl -H "Cache: no" -d "This message won't be stored server-side" ntfy.sh/mytopic
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ --no-cache \
+ mytopic "This message won't be stored server-side"
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /mytopic HTTP/1.1
+ Host: ntfy.sh
+ Cache: no
+
+ This message won't be stored server-side
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/mytopic', {
+ method: 'POST',
+ body: 'This message won't be stored server-side',
+ headers: { 'Cache': 'no' }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ req, _ := http.NewRequest("POST", "https://ntfy.sh/mytopic", strings.NewReader("This message won't be stored server-side"))
+ req.Header.Set("Cache", "no")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/mytopic"
+ $headers = @{ Cache="no" }
+ $body = "This message won't be stored server-side"
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Body $body -Headers $headers -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/mytopic",
+ data="This message won't be stored server-side",
+ headers={ "Cache": "no" })
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' =>
+ "Content-Type: text/plain\r\n" .
+ "Cache: no",
+ 'content' => 'This message won't be stored server-side'
+ ]
+ ]));
+ ```
+
+### Disable Firebase
+!!! info
+ If `Firebase: no` is used and [instant delivery](subscribe/phone.md#instant-delivery) isn't enabled in the Android
+ app (Google Play variant only), **message delivery will be significantly delayed (up to 15 minutes)**. To overcome
+ this delay, simply enable instant delivery.
+
+The ntfy server can be configured to use [Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging)
+(see [Firebase config](config.md#firebase-fcm)) for message delivery on Android (to minimize the app's battery footprint).
+The ntfy.sh server is configured this way, meaning that all messages published to ntfy.sh are also published to corresponding
+FCM topics.
+
+If you'd like to avoid forwarding messages to Firebase, you can set the `X-Firebase` header (or its alias: `Firebase`)
+to `no`. This will instruct the server not to forward messages to Firebase.
+
+=== "Command line (curl)"
+ ```
+ curl -H "X-Firebase: no" -d "This message won't be forwarded to FCM" ntfy.sh/mytopic
+ curl -H "Firebase: no" -d "This message won't be forwarded to FCM" ntfy.sh/mytopic
+ ```
+
+=== "ntfy CLI"
+ ```
+ ntfy publish \
+ --no-firebase \
+ mytopic "This message won't be forwarded to FCM"
+ ```
+
+=== "HTTP"
+ ``` http
+ POST /mytopic HTTP/1.1
+ Host: ntfy.sh
+ Firebase: no
+
+ This message won't be forwarded to FCM
+ ```
+
+=== "JavaScript"
+ ``` javascript
+ fetch('https://ntfy.sh/mytopic', {
+ method: 'POST',
+ body: 'This message won't be forwarded to FCM',
+ headers: { 'Firebase': 'no' }
+ })
+ ```
+
+=== "Go"
+ ``` go
+ req, _ := http.NewRequest("POST", "https://ntfy.sh/mytopic", strings.NewReader("This message won't be forwarded to FCM"))
+ req.Header.Set("Firebase", "no")
+ http.DefaultClient.Do(req)
+ ```
+
+=== "PowerShell"
+ ``` powershell
+ $uri = "https://ntfy.sh/mytopic"
+ $headers = @{ Firebase="no" }
+ $body = "This message won't be forwarded to FCM"
+ Invoke-RestMethod -Method 'Post' -Uri $uri -Body $body -Headers $headers -UseBasicParsing
+ ```
+
+=== "Python"
+ ``` python
+ requests.post("https://ntfy.sh/mytopic",
+ data="This message won't be forwarded to FCM",
+ headers={ "Firebase": "no" })
+ ```
+
+=== "PHP"
+ ``` php-inline
+ file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' =>
+ "Content-Type: text/plain\r\n" .
+ "Firebase: no",
+ 'content' => 'This message won't be stored server-side'
+ ]
+ ]));
+ ```
+
+### UnifiedPush
+!!! info
+ This setting is not relevant to users, only to app developers and people interested in [UnifiedPush](https://unifiedpush.org).
+
+[UnifiedPush](https://unifiedpush.org) is a standard for receiving push notifications without using the Google-owned
+[Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging) service. It puts push notifications
+in the control of the user. ntfy can act as a **UnifiedPush distributor**, forwarding messages to apps that support it.
+
+When publishing messages to a topic, apps using ntfy as a UnifiedPush distributor can set the `X-UnifiedPush` header or query
+parameter (or any of its aliases `unifiedpush` or `up`) to `1` to [disable Firebase](#disable-firebase). As of today, this
+option is mostly equivalent to `Firebase: no`, but was introduced to allow future flexibility. The flag additionally
+enables auto-detection of the message encoding. If the message is binary, it'll be encoded as base64.
+
+## Public topics
+Obviously all topics on ntfy.sh are public, but there are a few designated topics that are used in examples, and topics
+that you can use to try out what [authentication and access control](#authentication) looks like.
+
+| Topic | User | Permissions | Description |
+|------------------------------------------------|-----------------------------------|------------------------------------------------------|--------------------------------------|
+| [announcements](https://ntfy.sh/announcements) | `*` (unauthenticated) | Read-only for everyone | Release announcements and such |
+| [stats](https://ntfy.sh/stats) | `*` (unauthenticated) | Read-only for everyone | Daily statistics about ntfy.sh usage |
+| [mytopic-rw](https://ntfy.sh/mytopic-rw) | `testuser` (password: `testuser`) | Read-write for `testuser`, no access for anyone else | Test topic |
+| [mytopic-ro](https://ntfy.sh/mytopic-ro) | `testuser` (password: `testuser`) | Read-only for `testuser`, no access for anyone else | Test topic |
+| [mytopic-wo](https://ntfy.sh/mytopic-wo) | `testuser` (password: `testuser`) | Write-only for `testuser`, no access for anyone else | Test topic |
+
+## Limitations
+There are a few limitations to the API to prevent abuse and to keep the server healthy. Almost all of these settings
+are configurable via the server side [rate limiting settings](config.md#rate-limiting). Most of these limits you won't run into,
+but just in case, let's list them all:
+
+| Limit | Description |
+|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Message length** | Each message can be up to 4,096 bytes long. Longer messages are treated as [attachments](#attachments). |
+| **Requests** | By default, the server is configured to allow 60 requests per visitor at once, and then refills the your allowed requests bucket at a rate of one request per 5 seconds. |
+| **E-mails** | By default, the server is configured to allow sending 16 e-mails per visitor at once, and then refills the your allowed e-mail bucket at a rate of one per hour. |
+| **Subscription limit** | By default, the server allows each visitor to keep 30 connections to the server open. |
+| **Attachment size limit** | By default, the server allows attachments up to 15 MB in size, up to 100 MB in total per visitor and up to 5 GB across all visitors. |
+| **Attachment expiry** | By default, the server deletes attachments after 3 hours and thereby frees up space from the total visitor attachment limit. |
+| **Attachment bandwidth** | By default, the server allows 500 MB of GET/PUT/POST traffic for attachments per visitor in a 24 hour period. Traffic exceeding that is rejected. |
+| **Total number of topics** | By default, the server is configured to allow 15,000 topics. The ntfy.sh server has higher limits though. |
+
+## List of all parameters
+The following is a list of all parameters that can be passed when publishing a message. Parameter names are **case-insensitive**,
+and can be passed as **HTTP headers** or **query parameters in the URL**. They are listed in the table in their canonical form.
+
+| Parameter | Aliases (case-insensitive) | Description |
+|-----------------|--------------------------------------------|-----------------------------------------------------------------------------------------------|
+| `X-Message` | `Message`, `m` | Main body of the message as shown in the notification |
+| `X-Title` | `Title`, `t` | [Message title](#message-title) |
+| `X-Priority` | `Priority`, `prio`, `p` | [Message priority](#message-priority) |
+| `X-Tags` | `Tags`, `Tag`, `ta` | [Tags and emojis](#tags-emojis) |
+| `X-Delay` | `Delay`, `X-At`, `At`, `X-In`, `In` | Timestamp or duration for [delayed delivery](#scheduled-delivery) |
+| `X-Click` | `Click` | URL to open when [notification is clicked](#click-action) |
+| `X-Attach` | `Attach`, `a` | URL to send as an [attachment](#attachments), as an alternative to PUT/POST-ing an attachment |
+| `X-Filename` | `Filename`, `file`, `f` | Optional [attachment](#attachments) filename, as it appears in the client |
+| `X-Email` | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail notifications](#e-mail-notifications) |
+| `X-Cache` | `Cache` | Allows disabling [message caching](#message-caching) |
+| `X-Firebase` | `Firebase` | Allows disabling [sending to Firebase](#disable-firebase) |
+| `X-UnifiedPush` | `UnifiedPush`, `up` | [UnifiedPush](#unifiedpush) publish option, only to be used by UnifiedPush apps |
+| `Authorization` | - | If supported by the server, you can [login to access](#authentication) protected topics |