Repository: tcort/markdown-link-check

Issue:
`mailto:` link with multiple emails isn't handled correctly
It looks `mailto:` link with multiple emails [might not have been handled correctly](https://github.com/materialsproject/pymatgen/actions/runs/12078446868/job/33682870202?pr=4201), 

The links has the following format (I removed the real email addresses for privacy concerns, but if there emails addresses are necessary to recreate this you could [check here](https://github.com/materialsproject/pymatgen/blob/31f1e1fb8cc1bb517d59ea8e484965bb641c42a0/README.md?plain=1#L92), it's public):
```
mailto:user_a@address,user_b@gmail.com,user_c@another.address?subject=someSubjectOfTheEmail
```

And I got the following error:
> [✖] mailto:user_a@address,user_b@gmail.com,user_c@another.address?subject=someSubjectOfTheEmail → Status: 400

I'm not sure if this is an [`isemail`](https://github.com/skeggse/isemail) issue though, but I noticed it's no longer maintained:
>  This repository has been archived by the owner on Oct 2, 2024. It is now read-only. 


Discussion:
@MikeMcC399: @DanielYang59 

> I'm not sure if this is an [isemail](https://github.com/skeggse/isemail) issue though, but I noticed it's no longer maintained:

- See https://github.com/tcort/markdown-link-check/issues/371@DanielYang59: Hi Mike,

Thanks for letting me know. I don't know much about the internal working of `markdown-link-check` so not sure if I should propagate the issue to actual worker (`isemail` or `node-email-verifier` or whatever)@MikeMcC399: @DanielYang59 

> Thanks for letting me know. I don't know much about the internal working of `markdown-link-check ` so not sure if I should propagate the issue to actual worker (`isemail` or `node-email-verifier` or whatever)

I'm not familiar with this in-depth, but It's probably an enhancement request for this repo. You can check yourself about the functionality of [node-email-verifier](https://www.npmjs.com/package/node-email-verifier), which looks like it only deals with a single e-mail address and not a complete `mailto:` link with multiple addresses and a subject field.

@smainil: the code of the email check is here https://github.com/tcort/link-check/blob/3263c3b65917375c209928ca2b1ad4102824da36/lib/proto/mailto.js#L8. It only checks the text between the `mailto:` and the `?` character as a single email. It should try to split the string using the comma separator and loop on each single email found@github-actions: This issue has been marked as stale because it has been open 60 days with no activity. It will be closed in 30 days unless the stale label is removed or someone adds a comment.@MikeMcC399: Based on https://github.com/tcort/markdown-link-check/issues/378#issuecomment-2522348292 this would still be an open issue as the referenced code https://github.com/tcort/link-check/blob/master/lib/proto/mailto.js was last changed in Oct 2024 when isemail was replaced.@github-actions: This issue has been marked as stale because it has been open 60 days with no activity. It will be closed in 30 days unless the stale label is removed or someone adds a comment.@MikeMcC399: This issue is still open and reproducible.

`markdown-link-check` does not respect the syntax of [RFC 6068 - The 'mailto' URI Scheme](https://datatracker.ietf.org/doc/html/rfc6068) for multiple e-mail addresses, and logs a `400` error.

```shell
cat > multiple-mail.md <<EOT
[send to one recipient](mailto:no-user1@gmail.com)
[send to two recipients](mailto:no-user1@gmail.com,no-user2@gmail.com)
EOT
npm install markdown-link-check@latest -g
markdown-link-check multiple-mail.md
```

```text
FILE: multiple-mail.md
  [✓] mailto:no-user1@gmail.com
  [✖] mailto:no-user1@gmail.com,no-user2@gmail.com

  2 links checked.

  ERROR: 1 dead links found!
  [✖] mailto:no-user1@gmail.com,no-user2@gmail.com → Status: 400
```
@github-actions: This issue has been marked as stale because it has been open 60 days with no activity. It will be closed in 90 days unless the stale label is removed or someone adds a comment.@MikeMcC399: The last release was [v3.13.7](https://github.com/tcort/markdown-link-check/releases/tag/v3.13.7) on March 11, 2025 which is 6 months ago.

I suggest to increase the stale-bot time-frame accordingly. There is no point in flagging an issue as stale after 2 months if the typical release cadence is greater than 2 months. Otherwise issues like this may get automatically closed, even though the bug / feature request is still relevant.

@DanielYang59: Yep I agree it's quite annoying if we need to add a random comment every now and often just to prevent the PR being closed by the bot...@DanielYang59: I would take on this task myself (instead of having to deal with the bot)....@MikeMcC399: @tcort / @DanielYang59 

- I believe that this issue has been prematurely closed due to PR https://github.com/tcort/link-check/pull/106 including an auto-close for this issue (https://github.com/tcort/markdown-link-check/issues/378)

- Now, although https://github.com/tcort/link-check/pull/106 resulted in the release of [link-check@5.5.0](https://github.com/tcort/link-check/tree/v5.5.0), this does not automatically fix `markdown-link-check`.

To fix `markdown-link-check` reliably, it would need https://github.com/tcort/markdown-link-check/blob/e6703d0783204e80a31ebe2097b96b3165c3907f/package.json#L39 bumping to `"link-check": "^5.5.0"` and a new release of `markdown-link-check` would need to be cut. If that isn't done, only new installations of `markdown-link-check` will get the fix. Existing installations won't get notified or updated about a fix.



Solution patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1e732d4..96bdf15 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,10 +17,9 @@ jobs:
           - windows-latest
           - macos-latest
         node-version:
-          - 18
           - 20
           - 22
-          - 23
+          - 24
 
     env:
       NODE_OPTIONS: '--dns-result-order=ipv4first'
diff --git a/package-lock.json b/package-lock.json
index ea6c128..d7f8c2a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
         "async": "^3.2.6",
         "chalk": "^5.3.0",
         "commander": "^14.0.0",
-        "link-check": "^5.4.0",
+        "link-check": "^5.5.0",
         "markdown-link-extractor": "^4.0.2",
         "needle": "^3.3.1",
         "progress": "^2.0.3",
@@ -147,9 +147,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "9.35.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz",
-      "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==",
+      "version": "9.36.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz",
+      "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -1094,9 +1094,9 @@
       }
     },
     "node_modules/eslint": {
-      "version": "9.35.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz",
-      "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==",
+      "version": "9.36.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz",
+      "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1106,7 +1106,7 @@
         "@eslint/config-helpers": "^0.3.1",
         "@eslint/core": "^0.15.2",
         "@eslint/eslintrc": "^3.3.1",
-        "@eslint/js": "9.35.0",
+        "@eslint/js": "9.36.0",
         "@eslint/plugin-kit": "^0.3.5",
         "@humanfs/node": "^0.16.6",
         "@humanwhocodes/module-importer": "^1.0.1",
@@ -1184,6 +1184,19 @@
         "url": "https://opencollective.com/eslint"
       }
     },
+    "node_modules/eslint/node_modules/@eslint/js": {
+      "version": "9.35.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz",
+      "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://eslint.org/donate"
+      }
+    },
     "node_modules/eslint/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -1975,9 +1988,10 @@
       }
     },
     "node_modules/link-check": {
-      "version": "5.4.0",
-      "resolved": "https://registry.npmjs.org/link-check/-/link-check-5.4.0.tgz",
-      "integrity": "sha512-0Pf4xBVUnwJdbDgpBlhHNmWDtbVjHTpIFs+JaBuIsC9PKRxjv4KMGCO2Gc8lkVnqMf9B/yaNY+9zmMlO5MyToQ==",
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/link-check/-/link-check-5.5.0.tgz",
+      "integrity": "sha512-CpMk2zMfyEMdDvFG92wO5pU/2I/wbw72/9pvUFhU9cDKkwhmVlPuvxQJzd/jXA2iVOgNgPLnS5zyOLW7OzNpdA==",
+      "license": "ISC",
       "dependencies": {
         "is-relative-url": "^4.0.0",
         "ms": "^2.1.3",
diff --git a/package.json b/package.json
index b75b2d9..f79a131 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
     "async": "^3.2.6",
     "chalk": "^5.3.0",
     "commander": "^14.0.0",
-    "link-check": "^5.4.0",
+    "link-check": "^5.5.0",
     "markdown-link-extractor": "^4.0.2",
     "needle": "^3.3.1",
     "progress": "^2.0.3",



Test patch:
diff --git a/test/markdown-link-check.test.js b/test/markdown-link-check.test.js
index fdcfca2..06a8768 100644
--- a/test/markdown-link-check.test.js
+++ b/test/markdown-link-check.test.js
@@ -145,8 +145,14 @@ describe('markdown-link-check', function () {
                 // valid e-mail
                 { statusCode: 200, status:  'alive' },
 
+                // valid e-mail with multiple recipients
+                { statusCode: 200, status: 'alive' },
+
                 // invalid e-mail
                 { statusCode: 400, status:  'dead' },
+                
+                // invalid e-mail with multiple recipients
+                { statusCode: 400, status: 'dead' },
 
                 // invalid protocol
                 { statusCode: 500, status:  'error' },
diff --git a/test/sample.md b/test/sample.md
index ddc7d39..830803f 100644
--- a/test/sample.md
+++ b/test/sample.md
@@ -20,7 +20,9 @@ This is a test file:
 Here are some e-mail addresses:
 
 * [valid](mailto:linuxgeek@gmail.com?subject=test)
+* [valid with multiple recipients](MAILTO:alice@example.org,bob@example.net?subject=Test)
 * [invalid](mailto:foo@bar@baz)
+* [invalid with multiple recipients](mailto:foo@@bar@@baz,bob@example.net?subject=Test)
 
 Here are some invalid protocols: