parser.add_argument(
         "--related-to-html-only",
         action="store_true",
-        help="Make related content be sibling to HTML parts only"
+        help="Make related content be sibling to HTML parts only",
+    )
+
+    def positive_integer(value):
+        try:
+            if int(value) > 0:
+                return int(value)
+
+        except ValueError:
+            pass
+
+        raise ValueError(f"Must be a positive integer")
+
+    parser.add_argument(
+        "--max-number-other-attachments",
+        type=positive_integer,
+        help="Make related content be sibling to HTML parts only",
     )
 
     parser.add_argument(
         self._walk(
             root,
             ancestry=[],
+            descendents=[],
             visitor_fn=visitor_fn or self._visitor_fn,
         )
 
-    def _walk(self, node, *, ancestry, visitor_fn):
+    def _walk(self, node, *, ancestry, descendents, visitor_fn):
         # Let's start by enumerating the parts at the current level. At the
         # root level, ancestry will be the empty list, and we expect a
         # multipart/* container at this level. Later, e.g. within a
                 self._walk(
                     child,
                     ancestry=ancestry,
+                    descendents=descendents,
                     visitor_fn=visitor_fn,
                 )
             assert ancestry.pop() == node
+            sibling_descendents = descendents
+            descendents.extend(node.children)
 
         else:
             self.debugprint(f"{lead}├{node}")
+            sibling_descendents = descendents
 
         if False and ancestry:
             self.debugprint(lead[:-1] + " │")
 
         if visitor_fn:
-            visitor_fn(node, ancestry, debugprint=self.debugprint)
+            visitor_fn(
+                node, ancestry, sibling_descendents, debugprint=self.debugprint
+            )
 
     def debugprint(self, s, **kwargs):
         if self._debug:
     converter=convert_markdown_to_html,
     related_to_html_only=True,
     only_build=False,
+    max_other_attachments=20,
     tempdir=None,
     debug_commands=False,
     debug_walk=False,
 
     state = dict(pos=1, tags={}, parts=1)
 
-    def visitor_fn(item, ancestry, *, debugprint=None):
+    def visitor_fn(item, ancestry, descendents, *, debugprint=None):
         """
         Visitor function called for every node (part) of the MIME tree,
         depth-first, and responsible for telling NeoMutt how to assemble
             if item.cid:
                 cmds.push(f"<edit-content-id>{KILL_LINE}{item.cid}<enter>")
 
+            # Now for the biggest hack in this script, which is to handle
+            # attachments, such as PDFs, that aren't related or alternatives.
+            # The problem is that when we add an inline image, it always gets
+            # appended to the list, i.e. inserted *after* other attachments.
+            # Since we don't know the number of attachments, we also cannot
+            # infer the postition of the new attachment. Therefore, we bubble
+            # it all the way to the top, only to then move it down again:
+            if state["pos"] > 1:  # skip for the first part
+                for i in range(max_other_attachments):
+                    # could use any number here, but has to be larger than the
+                    # number of possible attachments. The performance
+                    # difference of using a high number is negligible.
+                    # Bubble up the new part
+                    cmds.push(f"<move-up>")
+
+                # As we push the part to the right position in the list (i.e.
+                # the last of the subset of attachments this script added), we
+                # must handle the situation that subtrees are skipped by
+                # NeoMutt. Hence, the actual number of positions to move down
+                # is decremented by the number of descendents so far
+                # encountered.
+                for i in range(1, state["pos"] - len(descendents)):
+                    cmds.push(f"<move-down>")
+
         elif isinstance(item, Multipart):
             # This node has children, but we already visited them (see
             # above). The tags dictionary of State should contain a list of
             # their positions in the NeoMutt compose window, so iterate those
             # and tag the parts there:
+            n_tags = len(state["tags"][item])
             for tag in state["tags"][item]:
                 cmds.push(f"<jump>{tag}<enter><tag-entry>")
 
                     f"Handling of multipart/{item.subtype} is not implemented"
                 )
 
-            state["pos"] -= len(state["tags"][item]) - 1
+            state["pos"] -= n_tags - 1
             state["parts"] += 1
-            del state["tags"][item]
 
         else:
             # We should never get here
             lead = "│ " * (len(ancestry) + 1) + "* "
             debugprint(
                 f"{lead}ancestry={[a.subtype for a in ancestry]}\n"
+                f"{lead}descendents={[d.subtype for d in descendents]}\n"
                 f"{lead}children_positions={state['tags'][ancestry[-1]]}\n"
                 f"{lead}pos={state['pos']}, parts={state['parts']}"
             )
                 extensions=args.extensions,
                 cssfile=args.css_file,
                 related_to_html_only=args.related_to_html_only,
+                max_other_attachments=args.max_number_other_attachments,
                 only_build=args.only_build,
                 tempdir=args.tempdir,
                 debug_commands=args.debug_commands,
 
             items = []
 
-            def visitor_fn(item, ancestry, debugprint):
-                items.append((item, len(ancestry)))
+            def visitor_fn(item, ancestry, descendents, debugprint):
+                items.append((item, len(ancestry), len(descendents)))
 
             mimetree.walk(
                 mime_tree_related_to_alternative, visitor_fn=visitor_fn
             assert len(items) == 5
             assert items[0][0].subtype == "plain"
             assert items[0][1] == 2
+            assert items[0][2] == 0
             assert items[1][0].subtype == "html"
             assert items[1][1] == 2
+            assert items[1][2] == 0
             assert items[2][0].subtype == "alternative"
             assert items[2][1] == 1
+            assert items[2][2] == 2
             assert items[3][0].subtype == "png"
             assert items[3][1] == 1
+            assert items[3][2] == 2
             assert items[4][0].subtype == "relative"
             assert items[4][1] == 0
+            assert items[4][2] == 4
 
         def test_MIMETreeDFWalker_list_to_mixed(self, const1):
             mimetree = MIMETreeDFWalker()
             items = []
 
-            def visitor_fn(item, ancestry, debugprint):
+            def visitor_fn(item, ancestry, descendents, debugprint):
                 items.append(item)
 
             p = Part("text", "plain", const1)
         ):
             items = []
 
-            def visitor_fn(item, ancestry, debugprint):
+            def visitor_fn(item, ancestry, descendents, debugprint):
                 items.append(item)
 
             mimetree = MIMETreeDFWalker(visitor_fn=visitor_fn)
             ):
                 return mime_tree_related_to_alternative
 
+            max_attachments = 5
             do_massage(
                 draft_f=string_io,
                 draftpath=const1,
                 cmd_f=sys.stdout,
+                max_other_attachments=max_attachments,
                 converter=converter,
             )
 
             assert "Plain" in lines.pop()
             assert "part.html" in lines.pop()
             assert "toggle-unlink" in lines.pop()
+            for i in range(max_attachments):
+                assert "move-up" in lines.pop()
+            assert "move-down" in lines.pop()
             assert "HTML" in lines.pop()
             assert "jump>1" in lines.pop()
             assert "jump>2" in lines.pop()
             assert "logo.png" in lines.pop()
             assert "toggle-unlink" in lines.pop()
             assert "content-id" in lines.pop()
+            for i in range(max_attachments):
+                assert "move-up" in lines.pop()
+            assert "move-down" in lines.pop()
             assert "Logo" in lines.pop()
             assert "jump>1" in lines.pop()
             assert "jump>4" in lines.pop()
             assert "Plain" in lines.pop()
             assert "part.html" in lines.pop()
             assert "toggle-unlink" in lines.pop()
+            assert "move-up" in lines.pop()
+            while True:
+                top = lines.pop()
+                if "move-up" not in top:
+                    break
+            assert "move-down" in top
             assert "HTML" in lines.pop()
             assert "logo.png" in lines.pop()
             assert "toggle-unlink" in lines.pop()
             assert "content-id" in lines.pop()
+            assert "move-up" in lines.pop()
+            while True:
+                top = lines.pop()
+                if "move-up" not in top:
+                    break
+            assert "move-down" in top
+            assert "move-down" in lines.pop()
             assert "Logo" in lines.pop()
             assert "jump>2" in lines.pop()
             assert "jump>3" in lines.pop()
             assert "send-message" in lines.pop()
             assert len(lines) == 0
 
+        def test_mime_tree_nested_trees_does_not_break_positioning(
+            self, string_io, const1, capsys
+        ):
+            def converter(
+                drafttext,
+                draftpath,
+                cssfile,
+                related_to_html_only,
+                extensions,
+                tempdir,
+            ):
+                return Multipart(
+                    "relative",
+                    children=[
+                        Multipart(
+                            "alternative",
+                            children=[
+                                Part(
+                                    "text",
+                                    "plain",
+                                    "part.txt",
+                                    desc="Plain",
+                                    orig=True,
+                                ),
+                                Multipart(
+                                    "alternative",
+                                    children=[
+                                        Part(
+                                            "text",
+                                            "plain",
+                                            "part.txt",
+                                            desc="Nested plain",
+                                        ),
+                                        Part(
+                                            "text",
+                                            "html",
+                                            "part.html",
+                                            desc="Nested HTML",
+                                        ),
+                                    ],
+                                    desc="Nested alternative",
+                                ),
+                            ],
+                            desc="Alternative",
+                        ),
+                        Part(
+                            "text",
+                            "png",
+                            "logo.png",
+                            cid="logo.png",
+                            desc="Logo",
+                        ),
+                    ],
+                    desc="Related",
+                )
+
+            do_massage(
+                draft_f=string_io,
+                draftpath=const1,
+                cmd_f=sys.stdout,
+                converter=converter,
+            )
+
+            captured = capsys.readouterr()
+            lines = captured.out.splitlines()
+            while not "logo.png" in lines.pop():
+                pass
+            lines.pop()
+            assert "content-id" in lines.pop()
+            assert "move-up" in lines.pop()
+            while True:
+                top = lines.pop()
+                if "move-up" not in top:
+                    break
+            assert "move-down" in top
+            # Due to the nested trees, the number of descendents of the sibling
+            # actually needs to be considered, not just the nieces. So to move
+            # from position 1 to position 6, it only needs one <move-down>
+            # because that jumps over the entire sibling tree. Thus what
+            # follows next must not be another <move-down>
+            assert "Logo" in lines.pop()
+
 except ImportError:
     pass