diff --git a/README.md b/README.md
index 8d98dd88..414aded4 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,7 @@ account costs. Even small donations are very much appreciated. A big fat **Thank
+
I'd also like to thank JetBrains for providing their awesome [IntelliJ IDEA](https://www.jetbrains.com/idea/) to me for free,
and [DigitalOcean](https://m.do.co/c/442b929528db) (*referral link*) for supporting the project:
diff --git a/docs/integrations.md b/docs/integrations.md
index 03c8229a..3eddb5e6 100644
--- a/docs/integrations.md
+++ b/docs/integrations.md
@@ -4,24 +4,6 @@ There are quite a few projects that work with ntfy, integrate ntfy, or have been
I've added a โญ to projects or posts that have a significant following, or had a lot of interaction by the community.
-## Public ntfy servers
-
-Here's a list of public ntfy servers. As of right now, there is only one official server. The others are provided by the
-ntfy community. Thanks to everyone running a public server. **You guys rock!**
-
-| URL | Country |
-|---------------------------------------------------|--------------------|
-| [ntfy.sh](https://ntfy.sh/) (*Official*) | ๐บ๐ธ United States |
-| [ntfy.tedomum.net](https://ntfy.tedomum.net/) | ๐ซ๐ท France |
-| [ntfy.jae.fi](https://ntfy.jae.fi/) | ๐ซ๐ฎ Finland |
-| [ntfy.adminforge.de](https://ntfy.adminforge.de/) | ๐ฉ๐ช Germany |
-| [ntfy.envs.net](https://ntfy.envs.net) | ๐ฉ๐ช Germany |
-| [ntfy.mzte.de](https://ntfy.mzte.de/) | ๐ฉ๐ช Germany |
-| [ntfy.hostux.net](https://ntfy.hostux.net/) | ๐ซ๐ท France |
-
-Please be aware that **server operators can log your messages**. The project also cannot guarantee the reliability
-and uptime of third party servers, so use of each server is **at your own discretion**.
-
## Official integrations
- [Healthchecks.io](https://healthchecks.io/) โญ - Online service for monitoring regularly running tasks such as cron jobs
@@ -42,6 +24,14 @@ and uptime of third party servers, so use of each server is **at your own discre
- [diun](https://crazymax.dev/diun/) - Docker Image Update Notifier
- [Cloudron](https://www.cloudron.io/store/sh.ntfy.cloudronapp.html) - Platform that makes it easy to manage web apps on your server
+## Integration via HTTP/SMTP/etc.
+
+- [Watchtower](https://containrrr.dev/watchtower/) โญ - Automating Docker container base image updates (see [integration example](examples.md#watchtower-shoutrrr))
+- [Jellyfin](https://jellyfin.org/) โญ - The Free Software Media System (see [integration example](examples.md#))
+- [Overseer](https://docs.overseerr.dev/using-overseerr/notifications/webhooks) โญ - a request management and media discovery tool for Plex (see [integration example](examples.md#jellyseerroverseerr-webhook))
+- [Tautulli](https://github.com/Tautulli/Tautulli) โญ - Monitoring and tracking tool for Plex (integration [via webhook](https://github.com/Tautulli/Tautulli/wiki/Notification-Agents-Guide#webhook))
+- [Mailrise](https://github.com/YoRyan/mailrise) - An SMTP gateway (integration via [Apprise](https://github.com/caronc/apprise/wiki/Notify_ntfy))
+
## [UnifiedPush](https://unifiedpush.org/users/apps/) integrations
- [Element](https://f-droid.org/packages/im.vector.app/) โญ - Matrix client
@@ -196,3 +186,22 @@ and uptime of third party servers, so use of each server is **at your own discre
- [ntfy otro sistema de notificaciones pub-sub simple basado en HTTP](https://ugeek.github.io/blog/post/2021-11-05-ntfy-sh-otro-sistema-de-notificaciones-pub-sub-simple-basado-en-http.html) - ugeek.github.io - 11/2021
- [Show HN: A tool to send push notifications to your phone, written in Go](https://news.ycombinator.com/item?id=29715464) โญ - news.ycombinator.com - 12/2021
- [Reddit selfhostable post](https://www.reddit.com/r/selfhosted/comments/qxlsm9/my_open_source_notification_android_app_and/) โญ - reddit.com - 11/2021
+
+
+## Alternative ntfy servers
+
+Here's a list of public ntfy servers. As of right now, there is only one official server. The others are provided by the
+ntfy community. Thanks to everyone running a public server. **You guys rock!**
+
+| URL | Country |
+|---------------------------------------------------|--------------------|
+| [ntfy.sh](https://ntfy.sh/) (*Official*) | ๐บ๐ธ United States |
+| [ntfy.tedomum.net](https://ntfy.tedomum.net/) | ๐ซ๐ท France |
+| [ntfy.jae.fi](https://ntfy.jae.fi/) | ๐ซ๐ฎ Finland |
+| [ntfy.adminforge.de](https://ntfy.adminforge.de/) | ๐ฉ๐ช Germany |
+| [ntfy.envs.net](https://ntfy.envs.net) | ๐ฉ๐ช Germany |
+| [ntfy.mzte.de](https://ntfy.mzte.de/) | ๐ฉ๐ช Germany |
+| [ntfy.hostux.net](https://ntfy.hostux.net/) | ๐ซ๐ท France |
+
+Please be aware that **server operators can log your messages**. The project also cannot guarantee the reliability
+and uptime of third party servers, so use of each server is **at your own discretion**.
diff --git a/docs/publish.md b/docs/publish.md
index 1f52cf3a..1d1109e4 100644
--- a/docs/publish.md
+++ b/docs/publish.md
@@ -2939,7 +2939,7 @@ To publish/subscribe to protected topics, you can:
When using Basic auth, base64 only encodes username and password. It **is not encrypting it**. For your
self-hosted server, **be sure to use HTTPS to avoid eavesdropping** and exposing your password.
-### Username & password
+### Username + password
The simplest way to authenticate against a ntfy server is to use [Basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication).
Here's an example with a user `testuser` and password `fakepassword`:
@@ -3549,10 +3549,10 @@ but just in case, let's list them all:
|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **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. |
-| **Daily messages** | By default, the number of messages is governed by the request limits. This can be overridden. On ntfy.sh, the daily message limit is 1,000. |
-| **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. On ntfy.sh, the daily limit is 10. |
+| **Daily messages** | By default, the number of messages is governed by the request limits. This can be overridden. On ntfy.sh, the daily message limit is 250. |
+| **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. On ntfy.sh, the daily limit is 5. |
| **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. On ntfy.sh, the attachment size limit is 5 MB, and the per-visitor total is 50 MB. |
+| **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. On ntfy.sh, the attachment size limit is 2 MB, and the per-visitor total is 20 MB. |
| **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. On ntfy.sh, the daily bandwidth limit is 200 MB. |
| **Total number of topics** | By default, the server is configured to allow 15,000 topics. The ntfy.sh server has higher limits though. |
diff --git a/docs/releases.md b/docs/releases.md
index 72cc39e5..37ccd4bc 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -1178,7 +1178,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
## Not released yet
-## ntfy server v2.5.0 (UNRELEASED)
+### ntfy server v2.5.0 (UNRELEASED)
**Features:**
@@ -1188,6 +1188,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
* Removed old ntfy website from ntfy entirely (no ticket)
* Fix potential subscriber ID clash ([#712](https://github.com/binwiederhier/ntfy/issues/712), thanks to [@peterbourgon](https://github.com/peterbourgon) for reporting, and [@dropdevrahul](https://github.com/dropdevrahul) for fixing)
+* Support for `quoted-printable` in incoming emails ([#719](https://github.com/binwiederhier/ntfy/pull/719), thanks to [@Aerion](https://github.com/Aerion))
### ntfy Android app v1.16.1 (UNRELEASED)
diff --git a/server/smtp_server.go b/server/smtp_server.go
index 16d97328..b9fbe6ee 100644
--- a/server/smtp_server.go
+++ b/server/smtp_server.go
@@ -9,6 +9,7 @@ import (
"io"
"mime"
"mime/multipart"
+ "mime/quotedprintable"
"net"
"net/http"
"net/http/httptest"
@@ -265,6 +266,8 @@ func readMultipartMailBody(body io.Reader, params map[string]string, depth int)
func readPlainTextMailBody(reader io.Reader, transferEncoding string) (string, error) {
if strings.ToLower(transferEncoding) == "base64" {
reader = base64.NewDecoder(base64.StdEncoding, reader)
+ } else if strings.ToLower(transferEncoding) == "quoted-printable" {
+ reader = quotedprintable.NewReader(reader)
}
body, err := io.ReadAll(reader)
if err != nil {
diff --git a/server/smtp_server_test.go b/server/smtp_server_test.go
index 49085d79..7e1d29d9 100644
--- a/server/smtp_server_test.go
+++ b/server/smtp_server_test.go
@@ -303,6 +303,39 @@ BBBBBBBBBBBBBBBBBBBBBBBBB`
writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
}
+func TestSmtpBackend_Plaintext_QuotedPrintable(t *testing.T) {
+ email := `EHLO example.com
+MAIL FROM: phil@example.com
+RCPT TO: mytopic@ntfy.sh
+DATA
+Date: Tue, 28 Dec 2021 00:30:10 +0100
+Message-ID:
+Subject: and one more
+From: Phil
+To: mytopic@ntfy.sh
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: quoted-printable
+
+what's
+=C3=A0&=C3=A9"'(-=C3=A8_=C3=A7=C3=A0)
+=3D=3D=3D=3D=3D
+up
+.
+`
+ s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
+ require.Equal(t, "/mytopic", r.URL.Path)
+ require.Equal(t, "and one more", r.Header.Get("Title"))
+ require.Equal(t, `what's
+ร &รฉ"'(-รจ_รงร )
+=====
+up`, readAll(t, r.Body))
+ })
+ conf.SMTPServerAddrPrefix = ""
+ defer s.Close()
+ defer c.Close()
+ writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
+}
+
func TestSmtpBackend_Unsupported(t *testing.T) {
email := `EHLO example.com
MAIL FROM: phil@example.com
@@ -390,6 +423,49 @@ L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
}
+func TestSmtpBackend_MultipartQuotedPrintable(t *testing.T) {
+ email := `EHLO example.com
+MAIL FROM: phil@example.com
+RCPT TO: ntfy-mytopic@ntfy.sh
+DATA
+MIME-Version: 1.0
+Date: Tue, 28 Dec 2021 00:30:10 +0100
+Message-ID:
+Subject: and one more
+From: Phil
+To: ntfy-mytopic@ntfy.sh
+Content-Type: multipart/alternative; boundary="000000000000f3320b05d42915c9"
+
+--000000000000f3320b05d42915c9
+Content-Type: text/html; charset="UTF-8"
+
+html, ignore me
+
+--000000000000f3320b05d42915c9
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: quoted-printable
+
+what's
+=C3=A0&=C3=A9"'(-=C3=A8_=C3=A7=C3=A0)
+=3D=3D=3D=3D=3D
+up
+
+--000000000000f3320b05d42915c9--
+.
+`
+ s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
+ require.Equal(t, "/mytopic", r.URL.Path)
+ require.Equal(t, "and one more", r.Header.Get("Title"))
+ require.Equal(t, `what's
+ร &รฉ"'(-รจ_รงร )
+=====
+up`, readAll(t, r.Body))
+ })
+ defer s.Close()
+ defer c.Close()
+ writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
+}
+
func TestSmtpBackend_NestedMultipartBase64(t *testing.T) {
email := `EHLO example.com
MAIL FROM: test@mydomain.me