diff --git a/pkg/services/ntfy/ntfy.go b/pkg/services/ntfy/ntfy.go
index 32e8d69d..ceb4a8da 100644
--- a/pkg/services/ntfy/ntfy.go
+++ b/pkg/services/ntfy/ntfy.go
@@ -30,6 +30,19 @@ func (service *Service) Send(message string, params *types.Params) error {
 		return err
 	}
 
+	// Prevent enabling templated messages without custom data
+	if config.Template && message == "" {
+		return fmt.Errorf("body must be set if template is enabled")
+	}
+
+	if config.Template && config.Message == "" && config.Title == "" {
+		return fmt.Errorf("at least title or message must be set if template is enabled")
+	}
+
+	if config.Message != "" && !config.Template {
+		return fmt.Errorf("message should not be filled if template is disabled, use body instead")
+	}
+
 	if err := service.sendAPI(config, message); err != nil {
 		return fmt.Errorf("failed to send ntfy notification: %w", err)
 	}
@@ -74,6 +87,10 @@ func (service *Service) sendAPI(config *Config, message string) error {
 	if !config.Firebase {
 		headers.Add("Firebase", "no")
 	}
+	if config.Template {
+		headers.Add("Template", "yes")
+		headers.Add("Message", config.Message)
+	}
 
 	if err := jsonClient.Post(config.GetAPIURL(), request, &response); err != nil {
 		if jsonClient.ErrorResponse(err, &response) {
diff --git a/pkg/services/ntfy/ntfy_config.go b/pkg/services/ntfy/ntfy_config.go
index 895c83da..728e859b 100644
--- a/pkg/services/ntfy/ntfy_config.go
+++ b/pkg/services/ntfy/ntfy_config.go
@@ -27,6 +27,8 @@ type Config struct {
 	Icon     string   `key:"icon"        optional:""         desc:"URL to use as notification icon"`
 	Cache    bool     `key:"cache"       default:"yes"       desc:"Cache messages"`
 	Firebase bool     `key:"firebase"    default:"yes"       desc:"Send to firebase"`
+	Template bool     `key:"template"    default:"no"        desc:"Use message and title as template"`
+	Message  string   `key:"message"     optional:""         desc:"Message, to be used only then template is set to true"`
 }
 
 // Enums implements types.ServiceConfig
diff --git a/pkg/services/ntfy/ntfy_test.go b/pkg/services/ntfy/ntfy_test.go
index 1719433c..3887d90b 100644
--- a/pkg/services/ntfy/ntfy_test.go
+++ b/pkg/services/ntfy/ntfy_test.go
@@ -71,16 +71,65 @@ var _ = Describe("the ntfy service", func() {
 					Priority: 3,
 					Firebase: true,
 					Cache:    true,
+					Template: false,
+					Message:  "",
 				}))
 			})
 		})
+
+		When("validate that template behaves correctly", func() {
+			BeforeEach(func() {
+				httpmock.Activate()
+			})
+			AfterEach(func() {
+				httpmock.DeactivateAndReset()
+			})
+
+			It("should not allow templated messages if message and title are not set", func() {
+				serviceURL := testutils.URLMust("ntfy://hostname/topic?template=true&message=&title=hello")
+				Expect(service.Initialize(serviceURL, logger)).ShouldNot(HaveOccurred())
+				Expect(service.Send("", nil)).Should(HaveOccurred())
+			})
+			It("should allow templated messages if title is set", func() {
+				serviceURL := testutils.URLMust("ntfy://:devicekey@hostname/topic?template=true&message=&title=hello")
+				Expect(service.Initialize(serviceURL, logger)).ShouldNot(HaveOccurred())
+
+				httpmock.RegisterResponder("POST", service.config.GetAPIURL(), testutils.JSONRespondMust(200, apiResponse{
+					Code:    http.StatusOK,
+					Message: "OK",
+				}))
+				Expect(service.Send("{}", nil)).To(Succeed())
+			})
+			It("should allow templated messages if message is set", func() {
+				serviceURL := testutils.URLMust("ntfy://:devicekey@hostname/topic?template=true&message=hello&title=")
+				Expect(service.Initialize(serviceURL, logger)).ShouldNot(HaveOccurred())
+
+				httpmock.RegisterResponder("POST", service.config.GetAPIURL(), testutils.JSONRespondMust(200, apiResponse{
+					Code:    http.StatusOK,
+					Message: "OK",
+				}))
+				Expect(service.Send("{}", nil)).To(Succeed())
+			})
+			It("should not allow templated messages if body is empty", func() {
+				serviceURL := testutils.URLMust("ntfy://hostname/topic?template=true&message=hello&title=hello")
+				Expect(service.Initialize(serviceURL, logger)).ShouldNot(HaveOccurred())
+				Expect(service.Send("", nil)).Should(HaveOccurred())
+			})
+			It("should validate that message is empty if template is disabled", func() {
+				serviceURL := testutils.URLMust("ntfy://hostname/topic?template=false&message=test")
+				Expect(service.Initialize(serviceURL, logger)).ShouldNot(HaveOccurred())
+				Expect(service.Send("test", nil)).Should(HaveOccurred())
+			})
+		})
+
 		When("parsing the configuration URL", func() {
 			It("should be identical after de-/serialization", func() {
-				testURL := "ntfy://user:pass@example.com:2225/topic?cache=No&click=CLICK&firebase=No&icon=ICON&priority=Max&scheme=http&title=TITLE"
+				testURL := "ntfy://user:pass@example.com:2225/topic?cache=No&click=CLICK&firebase=No&icon=ICON&priority=Max&scheme=http&template=Yes&title=TITLE"
 				config := &Config{}
 				pkr := format.NewPropKeyResolver(config)
 				Expect(config.setURL(&pkr, testutils.URLMust(testURL))).To(Succeed(), "verifying")
 				Expect(config.GetURL().String()).To(Equal(testURL))
+				Expect(config.Template).To(Equal(true))
 			})
 		})
 	})
@@ -132,7 +181,7 @@ var _ = Describe("the ntfy service", func() {
 				testutils.TestConfigSetDefaultValues(&Config{})
 
 				testutils.TestConfigGetEnumsCount(&Config{}, 1)
-				testutils.TestConfigGetFieldsCount(&Config{}, 15)
+				testutils.TestConfigGetFieldsCount(&Config{}, 17)
 			})
 		})
 		Describe("the service instance", func() {