diff --git a/client/options.go b/client/options.go
index dbca8c0e..7f6232f8 100644
--- a/client/options.go
+++ b/client/options.go
@@ -72,6 +72,11 @@ func WithAttach(attach string) PublishOption {
 	return WithHeader("X-Attach", attach)
 }
 
+// WithMarkdown instructs the server to interpret the message body as Markdown
+func WithMarkdown() PublishOption {
+	return WithHeader("X-Markdown", "yes")
+}
+
 // WithFilename sets a filename for the attachment, and/or forces the HTTP body to interpreted as an attachment
 func WithFilename(filename string) PublishOption {
 	return WithHeader("X-Filename", filename)
diff --git a/cmd/publish.go b/cmd/publish.go
index 0179f9fa..390e5f67 100644
--- a/cmd/publish.go
+++ b/cmd/publish.go
@@ -31,6 +31,7 @@ var flagsPublish = append(
 	&cli.StringFlag{Name: "icon", Aliases: []string{"i"}, EnvVars: []string{"NTFY_ICON"}, Usage: "URL to use as notification icon"},
 	&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
 	&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
+	&cli.BoolFlag{Name: "markdown", Aliases: []string{"md"}, EnvVars: []string{"NTFY_MARKDOWN"}, Usage: "Message is formatted as Markdown"},
 	&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the 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"},
@@ -95,6 +96,7 @@ func execPublish(c *cli.Context) error {
 	icon := c.String("icon")
 	actions := c.String("actions")
 	attach := c.String("attach")
+	markdown := c.Bool("attach")
 	filename := c.String("filename")
 	file := c.String("file")
 	email := c.String("email")
@@ -140,6 +142,9 @@ func execPublish(c *cli.Context) error {
 	if attach != "" {
 		options = append(options, client.WithAttach(attach))
 	}
+	if markdown {
+		options = append(options, client.WithMarkdown())
+	}
 	if filename != "" {
 		options = append(options, client.WithFilename(filename))
 	}
diff --git a/docs/publish.md b/docs/publish.md
index 905508fe..b20f66e6 100644
--- a/docs/publish.md
+++ b/docs/publish.md
@@ -138,7 +138,7 @@ a [title](#message-title), and [tag messages](#tags-emojis) 🥳 🎉. Here's an
         Tags = "warning,skull"
       }
       Body = "Remote access to phils-laptop detected. Act right away."
-    }             
+    }
     Invoke-RestMethod @Request
     ```
     
@@ -623,34 +623,108 @@ them with a comma, e.g. `tag1,tag2,tag3`.
     as [RFC 2047](https://datatracker.ietf.org/doc/html/rfc2047#section-2), e.g. `tag1,=?UTF-8?B?8J+HqfCfh6o=?=` ([base64](https://en.wikipedia.org/wiki/Base64)),
     or `=?UTF-8?Q?=C3=84pfel?=,tag2` ([quoted-printable](https://en.wikipedia.org/wiki/Quoted-printable)).
 
-## Markdown
+## Markdown formatting
 _Supported on:_ :material-firefox:
 
-You can format messages using [Markdown](https://www.markdownguide.org/basic-syntax/). 🤩
+You can format messages using [Markdown](https://www.markdownguide.org/basic-syntax/) 🤩. That means you can use 
+**bold**, *italicized*, or _underlined text_, links, images, and more. Supported Markdown features (web app only for now):
 
-By default, messages sent to ntfy are rendered as plain text. To enable Markdown, set the `X-Markdown` header (or any of 
-its aliases: `Markdown`, or `md`) to `true` (or `1` or `yes`), or set the `Content-Type` header to `text/markdown`. 
+- [Emphasis](https://www.markdownguide.org/basic-syntax/#emphasis) such as **bold** (`**bold**`), *italic* (`*italic*`), _underline_ (`_underline_`)
+- [Links](https://www.markdownguide.org/basic-syntax/#links) (`[some tool](https://ntfy.sh)`)
+- [Images](https://www.markdownguide.org/basic-syntax/#images) (`![some image](https://bing.com/logo.png)`)
+- [Code blocks](https://www.markdownguide.org/basic-syntax/#code-blocks) (` ```code blocks``` `) and [inline code](https://www.markdownguide.org/basic-syntax/#inline-code) (`` `inline code` ``)
+- [Headings](https://www.markdownguide.org/basic-syntax/#headings) (`# headings`, `## headings`, etc.)
+- [Lists](https://www.markdownguide.org/basic-syntax/#lists) (`- lists`, `1. lists`, etc.)
+- [Blockquotes](https://www.markdownguide.org/basic-syntax/#blockquotes) (`> blockquotes`)
+- [Horizontal rules](https://www.markdownguide.org/basic-syntax/#horizontal-rules) (`---`)
 
-Supported Markdown features:
+By default, messages sent to ntfy are rendered as plain text. To enable Markdown, set the `X-Markdown` header (or any of
+its aliases: `Markdown`, or `md`) to `true` (or `1` or `yes`), or set the `Content-Type` header to `text/markdown`.
+As of today, **Markdown is only supported in the web app.** Here's an example of how to enable Markdown formatting:
 
-- **bold** (`**bold**`)
-- *italic* (`*italic*`)
-- [links](https://www.markdownguide.org/basic-syntax/#links) (`[links](https://www.markdownguide.org/basic-syntax/#links)`)
-- [images](https://www.markdownguide.org/basic-syntax/#images) (`![images](https://www.markdownguide.org/basic-syntax/#images)`)
-- [code blocks](https://www.markdownguide.org/basic-syntax/#code-blocks) (`` `code blocks` ``)
-- [inline code](https://www.markdownguide.org/basic-syntax/#inline-code) (`` `inline code` ``)
-- [headings](https://www.markdownguide.org/basic-syntax/#headings) (`# headings`)
-- [lists](https://www.markdownguide.org/basic-syntax/#lists) (`- lists`)
-- [blockquotes](https://www.markdownguide.org/basic-syntax/#blockquotes) (`> blockquotes`)
-- [horizontal rules](https://www.markdownguide.org/basic-syntax/#horizontal-rules) (`---`)
+=== "Command line (curl)"
+    ```
+    curl \
+        -d "Look ma, **bold text**, *italics*, _underlined text_, ..." \
+        -H "Markdown: yes" \
+        ntfy.sh/mytopic
+    ```
 
-XXXXXXXXXXXXXXXXXXXXXx
-- examples
-- supported only on Web for now
+=== "ntfy CLI"
+    ```
+    ntfy publish \
+        mytopic \
+        --markdown \
+        "Look ma, **bold text**, *italics*, _underlined text_, ..."
+    ```
 
-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXxx
+=== "HTTP"
+    ``` http
+    POST /mytopic HTTP/1.1
+    Host: ntfy.sh
+    Markdown: yes
 
+    Look ma, **bold text**, *italics*, _underlined text_, ...
+    ```
 
+=== "JavaScript"
+    ``` javascript
+    fetch('https://ntfy.sh/mytopic', {
+      method: 'POST', // PUT works too
+      body: 'Look ma, **bold text**, *italics*, _underlined text_, ...',
+      headers: { 'Markdown': 'yes' }
+    })
+    ```
+
+=== "Go"
+    ``` go
+    http.Post("https://ntfy.sh/mytopic", "text/markdown",
+        strings.NewReader("Look ma, **bold text**, *italics*, _underlined text_, ..."))
+
+    // or
+    req, _ := http.NewRequest("POST", "https://ntfy.sh/mytopic", 
+        strings.NewReader("Look ma, **bold text**, *italics*, _underlined text_, ..."))
+    req.Header.Set("Markdown", "yes")
+    http.DefaultClient.Do(req)
+    ```
+
+=== "PowerShell"
+    ``` powershell
+    $Request = @{
+      Method = "POST"
+      URI = "https://ntfy.sh/mytopic"
+      Body = "Look ma, **bold text**, *italics*, _underlined text_, ..."
+      Headers = @{
+        Markdown = "yes"
+      }
+    }
+    Invoke-RestMethod @Request
+    ```
+
+=== "Python"
+    ``` python
+    requests.post("https://ntfy.sh/mytopic", 
+        data="Look ma, **bold text**, *italics*, _underlined text_, ..."
+        headers={ "Markdown": "yes" }))
+    ```
+
+=== "PHP"
+    ``` php-inline
+    file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
+        'http' => [
+            'method' => 'POST', // PUT also works
+            'header' => 'Content-Type: text/markdown', // !
+            'content' => 'Look ma, **bold text**, *italics*, _underlined text_, ...'
+        ]
+    ]));
+    ```
+
+Here's what that looks like in the web app:
+
+<figure markdown>
+  ![markdown](static/img/web-markdown.png){ width=500 }
+  <figcaption>Markdown formatting in the web app</figcaption>
+</figure>
 
 ## Scheduled delivery
 _Supported on:_ :material-android: :material-apple: :material-firefox:
diff --git a/docs/static/img/web-markdown.png b/docs/static/img/web-markdown.png
new file mode 100644
index 00000000..4e914fe8
Binary files /dev/null and b/docs/static/img/web-markdown.png differ
diff --git a/web/src/components/Notifications.jsx b/web/src/components/Notifications.jsx
index bd319dc5..e152cf20 100644
--- a/web/src/components/Notifications.jsx
+++ b/web/src/components/Notifications.jsx
@@ -15,6 +15,8 @@ import {
   IconButton,
   Box,
   Button,
+  useTheme,
+  ThemeProvider,
 } from "@mui/material";
 import * as React from "react";
 import { useEffect, useState } from "react";
@@ -37,7 +39,6 @@ import priority5 from "../img/priority-5.svg";
 import logoOutline from "../img/ntfy-outline.svg";
 import AttachmentIcon from "./AttachmentIcon";
 import { useAutoSubscribe } from "./hooks";
-import prefs from "../app/Prefs";
 
 const priorityFiles = {
   1: priority1,
@@ -174,7 +175,8 @@ const MarkdownContainer = styled("div")`
   p,
   pre,
   ul,
-  ol {
+  ol,
+  blockquote {
     margin: 0;
   }
 
@@ -182,14 +184,19 @@ const MarkdownContainer = styled("div")`
     line-height: 1.2;
   }
 
-  blockquote {
-    margin: 0;
-    padding-inline: 1rem;
-    background: ${(theme) => (theme.mode === "light" ? "#f1f1f1" : "#aeaeae")};
+  blockquote,
+  pre {
+    border-radius: 3px;
+    background: ${(props) => (props.theme.palette.mode === "light" ? "#f5f5f5" : "#333")};
+  }
+
+  pre {
+    padding: 0.9rem;
   }
 
   ul,
-  ol {
+  ol,
+  blockquote {
     padding-inline: 1rem;
   }