diff --git a/docs/publish.md b/docs/publish.md index 22329cdf..2518b95a 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:
- ![basic notification](static/img/basic-notification.png){ width=500 } + ![basic notification](static/img/android-screenshot-basic-notification.png){ width=500 }
Android notification
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 headerMessage will be delivered atExplanation
30m12/10/2021, 9:30am30 minutes from now
2 hours12/10/2021, 11:30am2 hours from now
1 day12/11/2021, 9am24 hours from now
10am12/10/2021, 10amToday at 10am (same day, because it's only 9am)
8am12/11/2021, 8amTomorrow at 8am (because it's 9am already)
163915200012/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: + +
+ ![image attachment](static/img/android-screenshot-attachment-image.png){ width=500 } +
Image attachment sent from a local file
+
+ +### 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", + ] + ])); + ``` + +
+ ![file attachment](static/img/android-screenshot-attachment-file.png){ width=500 } +
File attachment sent from an external URL
+
+ +## 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 notification](static/img/screenshot-email.png){ width=600 } +
E-mail notification
+
+ +## 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)): + +
+ ![e-mail publishing](static/img/screenshot-email-publishing-gmail.png){ width=500 } +
Publishing a message via e-mail
+
+ +## 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 |