diff --git a/ietf/utils/markdown.py b/ietf/utils/markdown.py
index 3b7c60cae..63d1c7a70 100644
--- a/ietf/utils/markdown.py
+++ b/ietf/utils/markdown.py
@@ -6,6 +6,8 @@ Use this instead of importing markdown directly to guarantee consistent extensio
 the datatracker.
 """
 import markdown as python_markdown
+from markdown.extensions import Extension
+from markdown.postprocessors import Postprocessor
 
 from django.utils.safestring import mark_safe
 
@@ -13,15 +15,37 @@ from ietf.doc.templatetags.ietf_filters import urlize_ietf_docs
 from ietf.utils.text import bleach_cleaner, bleach_linker
 
 
+class LinkifyExtension(Extension):
+    """
+    Simple Markdown extension inspired by https://github.com/daGrevis/mdx_linkify,
+    but using our bleach_linker directly. Doing the linkification on the converted
+    Markdown output introduces artifacts.
+    """
+
+    def extendMarkdown(self, md):
+        md.postprocessors.register(LinkifyPostprocessor(md), "linkify", 50)
+        # disable automatic links via angle brackets for email addresses
+        md.inlinePatterns.deregister("automail")
+        # "autolink" for URLs does not seem to cause issues, so leave it on
+
+
+class LinkifyPostprocessor(Postprocessor):
+    def run(self, text):
+        return urlize_ietf_docs(bleach_linker.linkify(text))
+
+
 def markdown(text):
     return mark_safe(
-        bleach_linker.linkify(
-            urlize_ietf_docs(
-                bleach_cleaner.clean(
-                    python_markdown.markdown(
-                        text, extensions=["extra", "nl2br", "sane_lists", "toc"]
-                    )
-                )
+        bleach_cleaner.clean(
+            python_markdown.markdown(
+                text,
+                extensions=[
+                    "extra",
+                    "nl2br",
+                    "sane_lists",
+                    "toc",
+                    LinkifyExtension(),
+                ],
             )
         )
     )
diff --git a/ietf/utils/tests_markdown.py b/ietf/utils/tests_markdown.py
new file mode 100644
index 000000000..c8c07b50c
--- /dev/null
+++ b/ietf/utils/tests_markdown.py
@@ -0,0 +1,60 @@
+# Copyright The IETF Trust 2023, All Rights Reserved
+"""Markdown API utilities tests"""
+
+from textwrap import dedent
+
+from ietf.utils.tests import TestCase
+from ietf.utils.markdown import markdown
+
+
+class MarkdownTests(TestCase):
+    SAMPLE_MARKDOWN = dedent(
+        """
+        # IETF Markdown Test File
+
+        This file contains a bunch of constructs to test our markdown converter in
+        `ietf/utils/markdown.py`.
+
+        ## Links
+
+        * https://example.com
+        * <https://example.com>
+        * [Example](https://example.com)
+        * user@example.com
+        * <user@example.com>
+        * [User](mailto:user@example.com)
+        * RFC2119
+        * BCP 3
+        * STD  1
+        * FYI2
+        * draft-ietf-opsec-indicators-of-compromise
+        * draft-ietf-opsec-indicators-of-compromise-01
+        """
+    )
+
+    SAMPLE_MARKDOWN_OUTPUT = dedent(
+        """
+        <h1 id="ietf-markdown-test-file">IETF Markdown Test File</h1>
+        <p>This file contains a bunch of constructs to test our markdown converter in<br>
+        <code>ietf/utils/<a href="http://markdown.py">markdown.py</a></code>.</p>
+        <h2 id="links">Links</h2>
+        <ul>
+        <li><a href="https://example.com">https://example.com</a></li>
+        <li><a href="https://example.com">https://example.com</a></li>
+        <li><a href="https://example.com">Example</a></li>
+        <li><a href="mailto:user@example.com">user@example.com</a></li>
+        <li>&lt;<a href="mailto:user@example.com">user@example.com</a>&gt;</li>
+        <li><a href="mailto:user@example.com">User</a></li>
+        <li>RFC2119</li>
+        <li>BCP 3</li>
+        <li>STD  1</li>
+        <li>FYI2</li>
+        <li>draft-ietf-opsec-indicators-of-compromise</li>
+        <li>draft-ietf-opsec-indicators-of-compromise-01</li>
+        </ul>
+        """
+    ).strip()
+
+    def test_markdown(self):
+        result = markdown(self.SAMPLE_MARKDOWN)
+        self.assertEqual(result, self.SAMPLE_MARKDOWN_OUTPUT)