26 KiB
Publishing
Publishing messages can be done via HTTP PUT/POST or via the ntfy CLI. 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
=== "ntfy CLI"
ntfy publish mytopic "Backup successful 😀"
=== "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 😀"))
=== "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([ 'http' => [ 'method' => 'POST', // PUT also works 'header' => 'Content-Type: text/plain', 'content' => 'Backup successful 😀' ] ]));
If you have the Android app 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, a title, and tag messages 🥳 🎉. Here's an example that uses some of them at together:
=== "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
=== "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 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)
=== "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([ '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 title
The notification title is typically set to the topic short URL (e.g. ntfy.sh/mytopic
). To override the title,
you can set the X-Title
header (or any of its aliases: Title
, ti
, or t
).
=== "Command line (curl)"
curl -H "X-Title: Dogs are better than cats" -d "Oh my ..." ntfy.sh/controversial curl -H "Title: Dogs are better than cats" -d "Oh my ..." ntfy.sh/controversial 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 Host: ntfy.sh Title: Dogs are better than cats
Oh my ...
```
=== "JavaScript"
javascript fetch('https://ntfy.sh/controversial', { method: 'POST', body: 'Oh my ...', headers: { 'Title': 'Dogs are better than cats' } })
=== "Go"
go req, _ := http.NewRequest("POST", "https://ntfy.sh/controversial", strings.NewReader("Oh my ...")) req.Header.Set("Title", "Dogs are better than cats") http.DefaultClient.Do(req)
=== "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([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: text/plain\r\n" . "Title: Dogs are better than cats", 'content' => 'Oh my ...' ] ]));
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).
The following priorities exist:
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
=== "ntfy CLI"
ntfy publish \ -p 5 \ phil_alerts An urgent message
=== "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)
=== "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([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: text/plain\r\n" . "Priority: 5", 'content' => 'An urgent message' ] ]));
Tags & emojis 🥳 🎉
You can tag messages with emojis and other relevant strings:
- Emojis: If a tag matches an emoji short code, it'll be converted to an emoji and prepended to title or message.
- Other tags: If a tag doesn't match, it will be listed below the notification.
This feature is useful for things like warnings (⚠️, ️🚨, or 🚩), but also to simply tag messages otherwise (e.g. script names, hostnames, etc.). Use the emoji short code list to figure out what tags can be converted to emojis. 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
, tag
, or ta
). Specify multiple tags by separating
them with a comma, e.g. tag1,tag2,tag3
.
=== "Command line (curl)"
curl -H "X-Tags: warning,mailsrv13,daily-backup" -d "Backup of mailsrv13 failed" ntfy.sh/backups curl -H "Tags: horse,unicorn" -d "Unicorns are just horses with unique horns" ntfy.sh/backups 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 Host: ntfy.sh Tags: warning,mailsrv13,daily-backup
Backup of mailsrv13 failed
```
=== "JavaScript"
javascript fetch('https://ntfy.sh/backups', { method: 'POST', body: 'Backup of mailsrv13 failed', headers: { 'Tags': 'warning,mailsrv13,daily-backup' } })
=== "Go"
go req, _ := http.NewRequest("POST", "https://ntfy.sh/backups", strings.NewReader("Backup of mailsrv13 failed")) req.Header.Set("Tags", "warning,mailsrv13,daily-backup") http.DefaultClient.Do(req)
=== "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([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: text/plain\r\n" . "Tags: warning,mailsrv13,daily-backup", 'content' => 'Backup of mailsrv13 failed' ] ]));
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).
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 if you'd like to change these limits).
For the purposes of message caching, 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 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)
=== "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):
|
Webhooks (Send 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, or if your client has limited HTTP support (e.g. like the 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 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")
=== "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, the message title, and tags as well.
For a full list of possible parameters, check the list of supported parameters and headers.
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")
=== "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');
Advanced features
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), 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=
and
poll=1
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)
=== "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 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) (see Firebase config) 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)
=== "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' ] ]));
Limitations
There are a few limitations to the API to prevent abuse and to keep the server healthy. Most of them you won't run into, but just in case, let's list them all:
Limit | Description |
---|---|
Message length | Each message can be up to 512 bytes long. Longer messages are truncated. |
Requests per second | By default, the server is configured to allow 60 requests at once, and then refills the your allowed requests bucket at a rate of one request per 10 seconds. You can read more about this in the rate limiting section. |
Subscription limits | By default, the server allows each visitor to keep 30 connections to the server open. |
Total number of topics | By default, the server is configured to allow 5,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.
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 |
X-Priority |
Priority , prio , p |
Message priority |
X-Tags |
Tags , ta |
Tags and emojis |
X-Delay |
Delay , X-At , At , X-In , In |
Timestamp or duration for delayed delivery |
X-Cache |
Cache |
Allows disabling message caching |
X-Firebase |
Firebase |
Allows disabling sending to Firebase |