mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-01-18 16:17:26 +01:00
Compare commits
5 Commits
2e39a1329c
...
94eb121f38
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94eb121f38 | ||
|
|
e81be48bf3 | ||
|
|
db4a4776d3 | ||
|
|
3d54260f79 | ||
|
|
ea3008c707 |
@@ -88,6 +88,11 @@ func WithFilename(filename string) PublishOption {
|
|||||||
return WithHeader("X-Filename", filename)
|
return WithHeader("X-Filename", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithSequenceID sets a sequence ID for the message, allowing updates to existing notifications
|
||||||
|
func WithSequenceID(sequenceID string) PublishOption {
|
||||||
|
return WithHeader("X-Sequence-ID", sequenceID)
|
||||||
|
}
|
||||||
|
|
||||||
// WithEmail instructs the server to also send the message to the given e-mail address
|
// WithEmail instructs the server to also send the message to the given e-mail address
|
||||||
func WithEmail(email string) PublishOption {
|
func WithEmail(email string) PublishOption {
|
||||||
return WithHeader("X-Email", email)
|
return WithHeader("X-Email", email)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ var flagsPublish = append(
|
|||||||
&cli.BoolFlag{Name: "markdown", Aliases: []string{"md"}, EnvVars: []string{"NTFY_MARKDOWN"}, Usage: "Message is formatted as Markdown"},
|
&cli.BoolFlag{Name: "markdown", Aliases: []string{"md"}, EnvVars: []string{"NTFY_MARKDOWN"}, Usage: "Message is formatted as Markdown"},
|
||||||
&cli.StringFlag{Name: "template", Aliases: []string{"tpl"}, EnvVars: []string{"NTFY_TEMPLATE"}, Usage: "use templates to transform JSON message body"},
|
&cli.StringFlag{Name: "template", Aliases: []string{"tpl"}, EnvVars: []string{"NTFY_TEMPLATE"}, Usage: "use templates to transform JSON message body"},
|
||||||
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
|
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
|
||||||
|
&cli.StringFlag{Name: "sequence-id", Aliases: []string{"sequence_id", "sid", "S"}, EnvVars: []string{"NTFY_SEQUENCE_ID"}, Usage: "sequence ID for updating notifications"},
|
||||||
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
|
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
|
||||||
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
|
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
|
||||||
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"},
|
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"},
|
||||||
@@ -70,6 +71,7 @@ Examples:
|
|||||||
ntfy pub --icon="http://some.tld/icon.png" 'Icon!' # Send notification with custom icon
|
ntfy pub --icon="http://some.tld/icon.png" 'Icon!' # Send notification with custom icon
|
||||||
ntfy pub --attach="http://some.tld/file.zip" files # Send ZIP archive from URL as attachment
|
ntfy pub --attach="http://some.tld/file.zip" files # Send ZIP archive from URL as attachment
|
||||||
ntfy pub --file=flower.jpg flowers 'Nice!' # Send image.jpg as attachment
|
ntfy pub --file=flower.jpg flowers 'Nice!' # Send image.jpg as attachment
|
||||||
|
ntfy pub -S my-id mytopic 'Update me' # Send with sequence ID for updates
|
||||||
echo 'message' | ntfy publish mytopic # Send message from stdin
|
echo 'message' | ntfy publish mytopic # Send message from stdin
|
||||||
ntfy pub -u phil:mypass secret Psst # Publish with username/password
|
ntfy pub -u phil:mypass secret Psst # Publish with username/password
|
||||||
ntfy pub --wait-pid 1234 mytopic # Wait for process 1234 to exit before publishing
|
ntfy pub --wait-pid 1234 mytopic # Wait for process 1234 to exit before publishing
|
||||||
@@ -101,6 +103,7 @@ func execPublish(c *cli.Context) error {
|
|||||||
markdown := c.Bool("markdown")
|
markdown := c.Bool("markdown")
|
||||||
template := c.String("template")
|
template := c.String("template")
|
||||||
filename := c.String("filename")
|
filename := c.String("filename")
|
||||||
|
sequenceID := c.String("sequence-id")
|
||||||
file := c.String("file")
|
file := c.String("file")
|
||||||
email := c.String("email")
|
email := c.String("email")
|
||||||
user := c.String("user")
|
user := c.String("user")
|
||||||
@@ -154,6 +157,9 @@ func execPublish(c *cli.Context) error {
|
|||||||
if filename != "" {
|
if filename != "" {
|
||||||
options = append(options, client.WithFilename(filename))
|
options = append(options, client.WithFilename(filename))
|
||||||
}
|
}
|
||||||
|
if sequenceID != "" {
|
||||||
|
options = append(options, client.WithSequenceID(sequenceID))
|
||||||
|
}
|
||||||
if email != "" {
|
if email != "" {
|
||||||
options = append(options, client.WithEmail(email))
|
options = append(options, client.WithEmail(email))
|
||||||
}
|
}
|
||||||
|
|||||||
568
docs/publish.md
568
docs/publish.md
@@ -937,6 +937,445 @@ Here's an example with a custom message, tags and a priority:
|
|||||||
file_get_contents('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull');
|
file_get_contents('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Updating + deleting notifications
|
||||||
|
_Supported on:_ :material-android: :material-firefox:
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
**This feature is not yet released.** It will be available in ntfy v2.16.x and later and ntfy Android v1.22.x and later.
|
||||||
|
|
||||||
|
You can **update, clear (mark as read and dismiss), or delete notifications** that have already been delivered. This is useful for scenarios
|
||||||
|
like download progress updates, replacing outdated information, or dismissing notifications that are no longer relevant.
|
||||||
|
|
||||||
|
* [Updating notifications](#updating-notifications) will alter the content of an existing notification.
|
||||||
|
* [Clearing notifications](#clearing-notifications) will mark them as read and dismiss them from the notification drawer.
|
||||||
|
* [Deleting notifications](#deleting-notifications) will remove them from the notification drawer and remove them in the clients as well (if supported).
|
||||||
|
|
||||||
|
Here's an example of a download progress notification being updated over time on Android:
|
||||||
|
|
||||||
|
<div id="updating-notifications-screenshots" class="screenshots">
|
||||||
|
<a href="../../static/img/android-screenshot-notification-update-1.png"><img src="../../static/img/android-screenshot-notification-update-1.png"/></a>
|
||||||
|
<a href="../../static/img/android-screenshot-notification-update-2.png"><img src="../../static/img/android-screenshot-notification-update-2.png"/></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
To facilitate updating notifications and altering existing notifications, ntfy messages are linked together in a sequence,
|
||||||
|
using a **sequence ID**. When a notification is meant to be updated, cleared, or deleted, you publish a new message with the
|
||||||
|
same sequence ID and the clients will perform the appropriate action on the existing notification.
|
||||||
|
|
||||||
|
Existing ntfy messages will not be updated on the server or in the message cache. Instead, a new message is created that indicates
|
||||||
|
the update, clear, or delete action. This append-only behavior ensures that message history remains intact.
|
||||||
|
|
||||||
|
### Updating notifications
|
||||||
|
To update an existing notification, publish a new message with the same sequence ID. Clients will replace the previous
|
||||||
|
notification with the new one. You can either:
|
||||||
|
|
||||||
|
1. **Use the message ID**: First publish like normal to `POST /<topic>` without a sequence ID, then use the returned message `id` as the sequence ID for updates
|
||||||
|
2. **Use a custom sequence ID**: Publish directly to `POST /<topic>/<sequence_id>` with your own identifier, or use `POST /<topic>` with the
|
||||||
|
`X-Sequence-ID` header (or any of its aliases: `Sequence-ID` or`SID`)
|
||||||
|
|
||||||
|
If you don't know the sequence ID ahead of time, you can publish a message first and then use the returned
|
||||||
|
message `id` to update it. Here's an example:
|
||||||
|
|
||||||
|
=== "Command line (curl)"
|
||||||
|
```bash
|
||||||
|
# First, publish a message and capture the message ID
|
||||||
|
curl -d "Downloading file..." ntfy.sh/mytopic
|
||||||
|
# Returns: {"id":"xE73Iyuabi","time":1673542291,...}
|
||||||
|
|
||||||
|
# Then use the message ID to update it (via URL path)
|
||||||
|
curl -d "Download 50% ..." ntfy.sh/mytopic/xE73Iyuabi
|
||||||
|
|
||||||
|
# Or update using the X-Sequence-ID header
|
||||||
|
curl -H "X-Sequence-ID: xE73Iyuabi" -d "Download complete" ntfy.sh/mytopic
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "ntfy CLI"
|
||||||
|
```bash
|
||||||
|
# First, publish a message and capture the message ID
|
||||||
|
ntfy pub mytopic "Downloading file..."
|
||||||
|
# Returns: {"id":"xE73Iyuabi","time":1673542291,...}
|
||||||
|
|
||||||
|
# Then use the message ID to update it
|
||||||
|
ntfy pub --sequence-id=xE73Iyuabi mytopic "Download 50% ..."
|
||||||
|
|
||||||
|
# Update again with the same sequence ID
|
||||||
|
ntfy pub -S xE73Iyuabi mytopic "Download complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "HTTP"
|
||||||
|
``` http
|
||||||
|
# First, publish a message and capture the message ID
|
||||||
|
POST /mytopic HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
|
|
||||||
|
Downloading file...
|
||||||
|
|
||||||
|
# Returns: {"id":"xE73Iyuabi","time":1673542291,...}
|
||||||
|
|
||||||
|
# Then use the message ID to update it
|
||||||
|
POST /mytopic/xE73Iyuabi HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
|
|
||||||
|
Download 50% ...
|
||||||
|
|
||||||
|
# Update again with the same sequence ID, this time using the header
|
||||||
|
POST /mytopic HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
|
X-Sequence-ID: xE73Iyuabi
|
||||||
|
|
||||||
|
Download complete
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "JavaScript"
|
||||||
|
``` javascript
|
||||||
|
// First, publish and get the message ID
|
||||||
|
const response = await fetch('https://ntfy.sh/mytopic', {
|
||||||
|
method: 'POST',
|
||||||
|
body: 'Downloading file...'
|
||||||
|
});
|
||||||
|
const { id } = await response.json();
|
||||||
|
|
||||||
|
// Update via URL path
|
||||||
|
await fetch(`https://ntfy.sh/mytopic/${id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: 'Download 50% ...'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Or update using the X-Sequence-ID header
|
||||||
|
await fetch('https://ntfy.sh/mytopic', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'X-Sequence-ID': id },
|
||||||
|
body: 'Download complete'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Go"
|
||||||
|
``` go
|
||||||
|
// Publish and parse the response to get the message ID
|
||||||
|
resp, _ := http.Post("https://ntfy.sh/mytopic", "text/plain",
|
||||||
|
strings.NewReader("Downloading file..."))
|
||||||
|
var msg struct { ID string `json:"id"` }
|
||||||
|
json.NewDecoder(resp.Body).Decode(&msg)
|
||||||
|
|
||||||
|
// Update via URL path
|
||||||
|
http.Post("https://ntfy.sh/mytopic/"+msg.ID, "text/plain",
|
||||||
|
strings.NewReader("Download 50% ..."))
|
||||||
|
|
||||||
|
// Or update using the X-Sequence-ID header
|
||||||
|
req, _ := http.NewRequest("POST", "https://ntfy.sh/mytopic",
|
||||||
|
strings.NewReader("Download complete"))
|
||||||
|
req.Header.Set("X-Sequence-ID", msg.ID)
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PowerShell"
|
||||||
|
``` powershell
|
||||||
|
# Publish and get the message ID
|
||||||
|
$response = Invoke-RestMethod -Method POST -Uri "https://ntfy.sh/mytopic" -Body "Downloading file..."
|
||||||
|
$messageId = $response.id
|
||||||
|
|
||||||
|
# Update via URL path
|
||||||
|
Invoke-RestMethod -Method POST -Uri "https://ntfy.sh/mytopic/$messageId" -Body "Download 50% ..."
|
||||||
|
|
||||||
|
# Or update using the X-Sequence-ID header
|
||||||
|
Invoke-RestMethod -Method POST -Uri "https://ntfy.sh/mytopic" `
|
||||||
|
-Headers @{"X-Sequence-ID"=$messageId} -Body "Download complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
``` python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Publish and get the message ID
|
||||||
|
response = requests.post("https://ntfy.sh/mytopic", data="Downloading file...")
|
||||||
|
message_id = response.json()["id"]
|
||||||
|
|
||||||
|
# Update via URL path
|
||||||
|
requests.post(f"https://ntfy.sh/mytopic/{message_id}", data="Download 50% ...")
|
||||||
|
|
||||||
|
# Or update using the X-Sequence-ID header
|
||||||
|
requests.post("https://ntfy.sh/mytopic",
|
||||||
|
headers={"X-Sequence-ID": message_id}, data="Download complete")
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PHP"
|
||||||
|
``` php-inline
|
||||||
|
// Publish and get the message ID
|
||||||
|
$response = file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
|
||||||
|
'http' => ['method' => 'POST', 'content' => 'Downloading file...']
|
||||||
|
]));
|
||||||
|
$messageId = json_decode($response)->id;
|
||||||
|
|
||||||
|
// Update via URL path
|
||||||
|
file_get_contents("https://ntfy.sh/mytopic/$messageId", false, stream_context_create([
|
||||||
|
'http' => ['method' => 'POST', 'content' => 'Download 50% ...']
|
||||||
|
]));
|
||||||
|
|
||||||
|
// Or update using the X-Sequence-ID header
|
||||||
|
file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'method' => 'POST',
|
||||||
|
'header' => "X-Sequence-ID: $messageId",
|
||||||
|
'content' => 'Download complete'
|
||||||
|
]
|
||||||
|
]));
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use a **custom sequence ID** (e.g., a download ID, job ID, etc.) when publishing the first message.
|
||||||
|
**This is less cumbersome**, since you don't need to capture the message ID first. Just publish directly to
|
||||||
|
`/<topic>/<sequence_id>`:
|
||||||
|
|
||||||
|
=== "Command line (curl)"
|
||||||
|
```bash
|
||||||
|
# Publish with a custom sequence ID
|
||||||
|
curl -d "Downloading file..." ntfy.sh/mytopic/my-download-123
|
||||||
|
|
||||||
|
# Update using the same sequence ID (via URL path)
|
||||||
|
curl -d "Download 50% ..." ntfy.sh/mytopic/my-download-123
|
||||||
|
|
||||||
|
# Or update using the X-Sequence-ID header
|
||||||
|
curl -H "X-Sequence-ID: my-download-123" -d "Download complete" ntfy.sh/mytopic
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "ntfy CLI"
|
||||||
|
```bash
|
||||||
|
# Publish with a sequence ID
|
||||||
|
ntfy pub --sequence-id=my-download-123 mytopic "Downloading file..."
|
||||||
|
|
||||||
|
# Update using the same sequence ID
|
||||||
|
ntfy pub --sequence-id=my-download-123 mytopic "Download 50% ..."
|
||||||
|
|
||||||
|
# Update again
|
||||||
|
ntfy pub -S my-download-123 mytopic "Download complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "HTTP"
|
||||||
|
``` http
|
||||||
|
# Publish a message with a custom sequence ID
|
||||||
|
POST /mytopic/my-download-123 HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
|
|
||||||
|
Downloading file...
|
||||||
|
|
||||||
|
# Update again using the X-Sequence-ID header
|
||||||
|
POST /mytopic HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
|
X-Sequence-ID: my-download-123
|
||||||
|
|
||||||
|
Download complete
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "JavaScript"
|
||||||
|
``` javascript
|
||||||
|
// First message
|
||||||
|
await fetch('https://ntfy.sh/mytopic/my-download-123', {
|
||||||
|
method: 'POST',
|
||||||
|
body: 'Downloading file...'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update via URL path
|
||||||
|
await fetch('https://ntfy.sh/mytopic/my-download-123', {
|
||||||
|
method: 'POST',
|
||||||
|
body: 'Download 50% ...'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Or update using the X-Sequence-ID header
|
||||||
|
await fetch('https://ntfy.sh/mytopic', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'X-Sequence-ID': 'my-download-123' },
|
||||||
|
body: 'Download complete'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Go"
|
||||||
|
``` go
|
||||||
|
// Publish with sequence ID in URL path
|
||||||
|
http.Post("https://ntfy.sh/mytopic/my-download-123", "text/plain",
|
||||||
|
strings.NewReader("Downloading file..."))
|
||||||
|
|
||||||
|
// Update via URL path
|
||||||
|
http.Post("https://ntfy.sh/mytopic/my-download-123", "text/plain",
|
||||||
|
strings.NewReader("Download 50% ..."))
|
||||||
|
|
||||||
|
// Or update using the X-Sequence-ID header
|
||||||
|
req, _ := http.NewRequest("POST", "https://ntfy.sh/mytopic",
|
||||||
|
strings.NewReader("Download complete"))
|
||||||
|
req.Header.Set("X-Sequence-ID", "my-download-123")
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PowerShell"
|
||||||
|
``` powershell
|
||||||
|
# Publish with sequence ID
|
||||||
|
Invoke-RestMethod -Method POST -Uri "https://ntfy.sh/mytopic/my-download-123" -Body "Downloading file..."
|
||||||
|
|
||||||
|
# Update via URL path
|
||||||
|
Invoke-RestMethod -Method POST -Uri "https://ntfy.sh/mytopic/my-download-123" -Body "Download 50% ..."
|
||||||
|
|
||||||
|
# Or update using the X-Sequence-ID header
|
||||||
|
Invoke-RestMethod -Method POST -Uri "https://ntfy.sh/mytopic" `
|
||||||
|
-Headers @{"X-Sequence-ID"="my-download-123"} -Body "Download complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
``` python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Publish with sequence ID
|
||||||
|
requests.post("https://ntfy.sh/mytopic/my-download-123", data="Downloading file...")
|
||||||
|
|
||||||
|
# Update via URL path
|
||||||
|
requests.post("https://ntfy.sh/mytopic/my-download-123", data="Download 50% ...")
|
||||||
|
|
||||||
|
# Or update using the X-Sequence-ID header
|
||||||
|
requests.post("https://ntfy.sh/mytopic",
|
||||||
|
headers={"X-Sequence-ID": "my-download-123"}, data="Download complete")
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PHP"
|
||||||
|
``` php-inline
|
||||||
|
// Publish with sequence ID
|
||||||
|
file_get_contents('https://ntfy.sh/mytopic/my-download-123', false, stream_context_create([
|
||||||
|
'http' => ['method' => 'POST', 'content' => 'Downloading file...']
|
||||||
|
]));
|
||||||
|
|
||||||
|
// Update via URL path
|
||||||
|
file_get_contents('https://ntfy.sh/mytopic/my-download-123', false, stream_context_create([
|
||||||
|
'http' => ['method' => 'POST', 'content' => 'Download 50% ...']
|
||||||
|
]));
|
||||||
|
|
||||||
|
// Or update using the X-Sequence-ID header
|
||||||
|
file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'method' => 'POST',
|
||||||
|
'header' => 'X-Sequence-ID: my-download-123',
|
||||||
|
'content' => 'Download complete'
|
||||||
|
]
|
||||||
|
]));
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also set the sequence ID via the `sequence-id` [query parameter](#list-of-all-parameters), or when
|
||||||
|
[publishing as JSON](#publish-as-json) using the `sequence_id` field.
|
||||||
|
|
||||||
|
If the message ID (`id`) and the sequence ID (`sequence_id`) are different, the ntfy server will include the `sequence_id`
|
||||||
|
field the response. A sequence of updates may look like this (first example from above):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"id":"xE73Iyuabi","time":1673542291,"event":"message","topic":"mytopic","message":"Downloading file..."}
|
||||||
|
{"id":"yF84Jzvbcj","time":1673542295,"event":"message","topic":"mytopic","sequence_id":"xE73Iyuabi","message":"Download 50% ..."}
|
||||||
|
{"id":"zG95Kawdde","time":1673542300,"event":"message","topic":"mytopic","sequence_id":"xE73Iyuabi","message":"Download complete"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clearing notifications
|
||||||
|
Clearing a notification means **marking it as read and dismissing it from the notification drawer**.
|
||||||
|
|
||||||
|
To do this, send a PUT request to the `/<topic>/<sequence_id>/clear` endpoint (or `/<topic>/<sequence_id>/read` as an alias).
|
||||||
|
This will then emit a `message_clear` event that is used by the clients (web app and Android app) to update the read status
|
||||||
|
and dismiss the notification.
|
||||||
|
|
||||||
|
=== "Command line (curl)"
|
||||||
|
```bash
|
||||||
|
curl -X PUT ntfy.sh/mytopic/my-download-123/clear
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "HTTP"
|
||||||
|
``` http
|
||||||
|
PUT /mytopic/my-download-123/clear HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "JavaScript"
|
||||||
|
``` javascript
|
||||||
|
await fetch('https://ntfy.sh/mytopic/my-download-123/clear', {
|
||||||
|
method: 'PUT'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Go"
|
||||||
|
``` go
|
||||||
|
req, _ := http.NewRequest("PUT", "https://ntfy.sh/mytopic/my-download-123/clear", nil)
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PowerShell"
|
||||||
|
``` powershell
|
||||||
|
Invoke-RestMethod -Method PUT -Uri "https://ntfy.sh/mytopic/my-download-123/clear"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
``` python
|
||||||
|
requests.put("https://ntfy.sh/mytopic/my-download-123/clear")
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PHP"
|
||||||
|
``` php-inline
|
||||||
|
file_get_contents('https://ntfy.sh/mytopic/my-download-123/clear', false, stream_context_create([
|
||||||
|
'http' => ['method' => 'PUT']
|
||||||
|
]));
|
||||||
|
```
|
||||||
|
|
||||||
|
An example response from the server with the `message_clear` event may look like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"id":"jkl012","time":1673542305,"event":"message_clear","topic":"mytopic","sequence_id":"my-download-123"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deleting notifications
|
||||||
|
Deleting a notification means **removing it from the notification drawer and from the client's database**.
|
||||||
|
|
||||||
|
To do this, send a DELETE request to the `/<topic>/<sequence_id>` endpoint. This will emit a `message_delete` event
|
||||||
|
that is used by the clients (web app and Android app) to remove the notification entirely.
|
||||||
|
|
||||||
|
=== "Command line (curl)"
|
||||||
|
```bash
|
||||||
|
curl -X DELETE ntfy.sh/mytopic/my-download-123
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "HTTP"
|
||||||
|
``` http
|
||||||
|
DELETE /mytopic/my-download-123 HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "JavaScript"
|
||||||
|
``` javascript
|
||||||
|
await fetch('https://ntfy.sh/mytopic/my-download-123', {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Go"
|
||||||
|
``` go
|
||||||
|
req, _ := http.NewRequest("DELETE", "https://ntfy.sh/mytopic/my-download-123", nil)
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PowerShell"
|
||||||
|
``` powershell
|
||||||
|
Invoke-RestMethod -Method DELETE -Uri "https://ntfy.sh/mytopic/my-download-123"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
``` python
|
||||||
|
requests.delete("https://ntfy.sh/mytopic/my-download-123")
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PHP"
|
||||||
|
``` php-inline
|
||||||
|
file_get_contents('https://ntfy.sh/mytopic/my-download-123', false, stream_context_create([
|
||||||
|
'http' => ['method' => 'DELETE']
|
||||||
|
]));
|
||||||
|
```
|
||||||
|
|
||||||
|
An example response from the server with the `message_delete` event may look like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"id":"mno345","time":1673542400,"event":"message_delete","topic":"mytopic","sequence_id":"my-download-123"}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
Deleted sequences can be revived by publishing a new message with the same sequence ID. The notification will
|
||||||
|
reappear as a new message.
|
||||||
|
|
||||||
## Message templating
|
## Message templating
|
||||||
_Supported on:_ :material-android: :material-apple: :material-firefox:
|
_Supported on:_ :material-android: :material-apple: :material-firefox:
|
||||||
|
|
||||||
@@ -2695,134 +3134,6 @@ Here's an example that will open Reddit when the notification is clicked:
|
|||||||
]));
|
]));
|
||||||
```
|
```
|
||||||
|
|
||||||
## Updating + deleting notifications
|
|
||||||
_Supported on:_ :material-android: :material-firefox:
|
|
||||||
|
|
||||||
You can update, clear (mark as read), or delete notifications that have already been delivered. This is useful for scenarios
|
|
||||||
like download progress updates, replacing outdated information, or dismissing notifications that are no longer relevant.
|
|
||||||
|
|
||||||
The key concept is the **sequence ID** (`sequence_id` or `sid`): notifications with the same sequence ID are treated as
|
|
||||||
belonging to the same sequence, and clients will update/replace the notification accordingly.
|
|
||||||
|
|
||||||
### Updating notifications
|
|
||||||
To update an existing notification, publish a new message with the same sequence ID. Clients will replace the previous
|
|
||||||
notification with the new one.
|
|
||||||
|
|
||||||
You can either:
|
|
||||||
|
|
||||||
1. **Use the message ID**: First publish without a sequence ID, then use the returned message `id` as the sequence ID for updates
|
|
||||||
2. **Use a custom sequence ID**: Publish directly to `/<topic>/<sequence_id>` with your own identifier
|
|
||||||
|
|
||||||
=== "Using the message ID"
|
|
||||||
```bash
|
|
||||||
# First, publish a message and capture the message ID
|
|
||||||
$ curl -d "Downloading file..." ntfy.sh/mytopic
|
|
||||||
{"id":"xE73Iyuabi","time":1673542291,...}
|
|
||||||
|
|
||||||
# Then use the message ID to update it
|
|
||||||
$ curl -d "Download complete!" ntfy.sh/mytopic/xE73Iyuabi
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "Using a custom sequence ID"
|
|
||||||
```bash
|
|
||||||
# Publish with a custom sequence ID
|
|
||||||
$ curl -d "Downloading file..." ntfy.sh/mytopic/my-download-123
|
|
||||||
|
|
||||||
# Update using the same sequence ID
|
|
||||||
$ curl -d "Download complete!" ntfy.sh/mytopic/my-download-123
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "Using the X-Sequence-ID header"
|
|
||||||
```bash
|
|
||||||
# Publish with a sequence ID via header
|
|
||||||
$ curl -H "X-Sequence-ID: my-download-123" -d "Downloading..." ntfy.sh/mytopic
|
|
||||||
|
|
||||||
# Update using the same sequence ID
|
|
||||||
$ curl -H "X-Sequence-ID: my-download-123" -d "Done!" ntfy.sh/mytopic
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also set the sequence ID via the `sid` query parameter or when [publishing as JSON](#publish-as-json) using the
|
|
||||||
`sequence_id` field.
|
|
||||||
|
|
||||||
### Clearing notifications
|
|
||||||
To clear a notification (mark it as read and dismiss it from the notification drawer), send a PUT request to
|
|
||||||
`/<topic>/<sequence_id>/clear` (or `/<topic>/<sequence_id>/read` as an alias):
|
|
||||||
|
|
||||||
=== "Command line (curl)"
|
|
||||||
```bash
|
|
||||||
curl -X PUT ntfy.sh/mytopic/my-download-123/clear
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "HTTP"
|
|
||||||
```http
|
|
||||||
PUT /mytopic/my-download-123/clear HTTP/1.1
|
|
||||||
Host: ntfy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This publishes a `message_clear` event, which tells clients to:
|
|
||||||
|
|
||||||
- Mark the notification as read in the app
|
|
||||||
- Dismiss the browser/Android notification
|
|
||||||
|
|
||||||
### Deleting notifications
|
|
||||||
To delete a notification entirely, send a DELETE request to `/<topic>/<sequence_id>`:
|
|
||||||
|
|
||||||
=== "Command line (curl)"
|
|
||||||
```bash
|
|
||||||
curl -X DELETE ntfy.sh/mytopic/my-download-123
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "HTTP"
|
|
||||||
```http
|
|
||||||
DELETE /mytopic/my-download-123 HTTP/1.1
|
|
||||||
Host: ntfy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This publishes a `message_delete` event, which tells clients to:
|
|
||||||
|
|
||||||
- Delete the notification from the database
|
|
||||||
- Dismiss the browser/Android notification
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
Deleted sequences can be revived by publishing a new message with the same sequence ID. The notification will
|
|
||||||
reappear as a new message.
|
|
||||||
|
|
||||||
### Full example
|
|
||||||
Here's a complete example showing the lifecycle of a notification with updates, clearing, and deletion:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Create a notification with a custom sequence ID
|
|
||||||
$ curl -d "Starting backup..." ntfy.sh/mytopic/backup-2024
|
|
||||||
|
|
||||||
# 2. Update the notification with progress
|
|
||||||
$ curl -d "Backup 50% complete..." ntfy.sh/mytopic/backup-2024
|
|
||||||
|
|
||||||
# 3. Update again when complete
|
|
||||||
$ curl -d "Backup finished successfully!" ntfy.sh/mytopic/backup-2024
|
|
||||||
|
|
||||||
# 4. Clear the notification (dismiss from notification drawer)
|
|
||||||
$ curl -X PUT ntfy.sh/mytopic/backup-2024/clear
|
|
||||||
|
|
||||||
# 5. Later, delete the notification entirely
|
|
||||||
$ curl -X DELETE ntfy.sh/mytopic/backup-2024
|
|
||||||
```
|
|
||||||
|
|
||||||
When polling the topic, you'll see the complete sequence of events:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ curl -s "ntfy.sh/mytopic/json?poll=1&since=all" | jq .
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"id":"abc123","time":1673542291,"event":"message","topic":"mytopic","sequence_id":"backup-2024","message":"Starting backup..."}
|
|
||||||
{"id":"def456","time":1673542295,"event":"message","topic":"mytopic","sequence_id":"backup-2024","message":"Backup 50% complete..."}
|
|
||||||
{"id":"ghi789","time":1673542300,"event":"message","topic":"mytopic","sequence_id":"backup-2024","message":"Backup finished successfully!"}
|
|
||||||
{"id":"jkl012","time":1673542305,"event":"message_clear","topic":"mytopic","sequence_id":"backup-2024"}
|
|
||||||
{"id":"mno345","time":1673542400,"event":"message_delete","topic":"mytopic","sequence_id":"backup-2024"}
|
|
||||||
```
|
|
||||||
|
|
||||||
Clients process these events in order, keeping only the latest state for each sequence ID.
|
|
||||||
|
|
||||||
## Attachments
|
## Attachments
|
||||||
_Supported on:_ :material-android: :material-firefox:
|
_Supported on:_ :material-android: :material-firefox:
|
||||||
|
|
||||||
@@ -4060,6 +4371,7 @@ table in their canonical form.
|
|||||||
|-----------------|--------------------------------------------|-----------------------------------------------------------------------------------------------|
|
|-----------------|--------------------------------------------|-----------------------------------------------------------------------------------------------|
|
||||||
| `X-Message` | `Message`, `m` | Main body of the message as shown in the notification |
|
| `X-Message` | `Message`, `m` | Main body of the message as shown in the notification |
|
||||||
| `X-Title` | `Title`, `t` | [Message title](#message-title) |
|
| `X-Title` | `Title`, `t` | [Message title](#message-title) |
|
||||||
|
| `X-Sequence-ID` | `Sequence-ID`, `SID` | [Sequence ID](#updating-deleting-notifications) for updating/clearing/deleting notifications |
|
||||||
| `X-Priority` | `Priority`, `prio`, `p` | [Message priority](#message-priority) |
|
| `X-Priority` | `Priority`, `prio`, `p` | [Message priority](#message-priority) |
|
||||||
| `X-Tags` | `Tags`, `Tag`, `ta` | [Tags and emojis](#tags-emojis) |
|
| `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-Delay` | `Delay`, `X-At`, `At`, `X-In`, `In` | Timestamp or duration for [delayed delivery](#scheduled-delivery) |
|
||||||
|
|||||||
18
docs/static/css/extra.css
vendored
18
docs/static/css/extra.css
vendored
@@ -1,10 +1,10 @@
|
|||||||
:root > * {
|
:root > * {
|
||||||
--md-primary-fg-color: #338574;
|
--md-primary-fg-color: #338574;
|
||||||
--md-primary-fg-color--light: #338574;
|
--md-primary-fg-color--light: #338574;
|
||||||
--md-primary-fg-color--dark: #338574;
|
--md-primary-fg-color--dark: #338574;
|
||||||
--md-footer-bg-color: #353744;
|
--md-footer-bg-color: #353744;
|
||||||
--md-text-font: "Roboto";
|
--md-text-font: "Roboto";
|
||||||
--md-code-font: "Roboto Mono";
|
--md-code-font: "Roboto Mono";
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-header__button.md-logo :is(img, svg) {
|
.md-header__button.md-logo :is(img, svg) {
|
||||||
@@ -34,7 +34,7 @@ figure img, figure video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
background: linear-gradient(150deg, rgba(51,133,116,1) 0%, rgba(86,189,168,1) 100%);
|
background: linear-gradient(150deg, rgba(51, 133, 116, 1) 0%, rgba(86, 189, 168, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-md-color-scheme="default"] header {
|
body[data-md-color-scheme="default"] header {
|
||||||
@@ -93,7 +93,7 @@ figure video {
|
|||||||
|
|
||||||
.screenshots img {
|
.screenshots img {
|
||||||
max-height: 230px;
|
max-height: 230px;
|
||||||
max-width: 300px;
|
max-width: 350px;
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
filter: drop-shadow(2px 2px 2px #ddd);
|
filter: drop-shadow(2px 2px 2px #ddd);
|
||||||
@@ -107,7 +107,7 @@ figure video {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left:0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -119,7 +119,7 @@ figure video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lightbox.show {
|
.lightbox.show {
|
||||||
background-color: rgba(0,0,0, 0.75);
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|||||||
BIN
docs/static/img/android-screenshot-notification-update-1.png
vendored
Normal file
BIN
docs/static/img/android-screenshot-notification-update-1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
docs/static/img/android-screenshot-notification-update-2.png
vendored
Normal file
BIN
docs/static/img/android-screenshot-notification-update-2.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
Reference in New Issue
Block a user