diff --git a/docs/config.md b/docs/config.md index 1f8a54d2..57625622 100644 --- a/docs/config.md +++ b/docs/config.md @@ -32,7 +32,7 @@ You can also entirely disable the cache by setting `cache-duration` to `0`. When passed on to the connected subscribers, but never stored on disk or even kept in memory longer than is needed to forward the message to the subscribers. -Subscribers can retrieve cached messaging using the [`poll=1` parameter](subscribe/api.md#polling), as well as the +Subscribers can retrieve cached messaging using the [`poll=1` parameter](subscribe/api.md#polling-for-messages), as well as the [`since=` parameter](subscribe/api.md#fetching-cached-messages). ## Behind a proxy (TLS, etc.) diff --git a/docs/publish.md b/docs/publish.md index 5ffaaf78..0e4a04d1 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -332,6 +332,85 @@ 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 + ``` + +=== "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) + ``` + +=== "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)
+
+ ## Advanced features ### Message caching @@ -347,7 +426,7 @@ client-side network disruptions, but arguably this feature also may raise privac 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#fetching-cached-messages) and -[`poll=1`](subscribe/api.md#polling) won't return the message anymore. +[`poll=1`](subscribe/api.md#polling-for-messages) won't return the message anymore. === "Command line (curl)" ``` @@ -393,7 +472,7 @@ are still delivered to connected subscribers, but [`since=`](subscribe/api.md#fe ])); ``` -### Firebase +### 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 diff --git a/docs/subscribe/api.md b/docs/subscribe/api.md index b4449cf2..4a0bb858 100644 --- a/docs/subscribe/api.md +++ b/docs/subscribe/api.md @@ -239,7 +239,7 @@ or `all` (all cached messages). curl -s "ntfy.sh/mytopic/json?since=10m" ``` -### Polling +### Polling for messages You can also just poll for messages if you don't like the long-standing connection using the `poll=1` query parameter. The connection will end after all available messages have been read. This parameter can be combined with `since=` (defaults to `since=all`). @@ -248,6 +248,16 @@ combined with `since=` (defaults to `since=all`). curl -s "ntfy.sh/mytopic/json?poll=1" ``` +### Fetching scheduled messages +Messages that are [scheduled to be delivered](../publish.md#scheduled-delivery) at a later date are not typically +returned when subscribing via the API, which makes sense, because after all, the messages have technically not been +delivered yet. To also return scheduled messages from the API, you can use the `scheduled=1` (alias: `sched=1`) +parameter (makes most sense with the `poll=1` parameter): + +``` +curl -s "ntfy.sh/mytopic/json?poll=1&sched=1" +``` + ### Subscribing to multiple topics It's possible to subscribe to multiple topics in one HTTP call by providing a comma-separated list of topics in the URL. This allows you to reduce the number of connections you have to maintain: diff --git a/server/server.go b/server/server.go index b5bd31ee..6fadd9d9 100644 --- a/server/server.go +++ b/server/server.go @@ -344,20 +344,20 @@ func (s *Server) parseHeaders(header http.Header, m *message) (cache bool, fireb m.Tags = append(m.Tags, strings.TrimSpace(s)) } } - whenStr := readHeader(header, "x-at", "at", "x-in", "in", "x-delay", "delay") - if whenStr != "" { + delayStr := readHeader(header, "x-delay", "delay", "x-at", "at", "x-in", "in") + if delayStr != "" { if !cache { return false, false, errHTTPBadRequest } - at, err := util.ParseFutureTime(whenStr, time.Now()) + delay, err := util.ParseFutureTime(delayStr, time.Now()) if err != nil { return false, false, errHTTPBadRequest - } else if at.Unix() < time.Now().Add(s.config.MinDelay).Unix() { + } else if delay.Unix() < time.Now().Add(s.config.MinDelay).Unix() { return false, false, errHTTPBadRequest - } else if at.Unix() > time.Now().Add(s.config.MaxDelay).Unix() { + } else if delay.Unix() > time.Now().Add(s.config.MaxDelay).Unix() { return false, false, errHTTPBadRequest } - m.Time = at.Unix() + m.Time = delay.Unix() } return cache, firebase, nil } diff --git a/util/time.go b/util/time.go index ed68b766..70501210 100644 --- a/util/time.go +++ b/util/time.go @@ -14,6 +14,8 @@ var ( durationStrRegex = regexp.MustCompile(`(?i)^(\d+)\s*(d|days?|h|hours?|m|mins?|minutes?|s|secs?|seconds?)$`) ) +// ParseFutureTime parses a date/time string to a time.Time. It supports unix timestamps, durations +// and natural language dates func ParseFutureTime(s string, now time.Time) (time.Time, error) { s = strings.TrimSpace(s) t, err := parseUnixTime(s, now)