Repository: go-acme/lego (language: go)

Issue:
Support for provider: mijn.host
### Welcome

- [X] Yes, I've searched similar issues on GitHub and didn't find any.
- [x] Yes, the DNS provider exposes a public API.
- [X] Yes, I know that the lego maintainers don't have an account in all DNS providers in the world.
- [ ] Yes, I'm able to create a pull request and be able to maintain the implementation.
- [X] Yes, I'm able to test an implementation if someone creates a pull request to add the support of this DNS provider.

### How do you use lego?

Binary

### Link to the DNS provider

https://mijn.host/

### Link to the API documentation

https://mijn.host/api/doc/api-3563906

### Additional Notes

I have the API key and a domain associated with it, I will be writing the code I just need your help to add this DNS provider.


Discussion / hints:
Hey @ldez whatever support you need to add this provider, I am always here to help you. I just want to get started with this as soon as possible.

Thanks
Your 2 PRs (#2250, #2249) are off-topic: adding the provider to the readme is useless if there is no implementation, also the readme is generated.

Either you know how to write a provider and create a PR or you don't know how to do it and will test a PR if someone doing one.

Spamming this repository with invalid PRs is not respectful.
I am sorry for that, also I was not aware that the README is auto generated. So, I created the draft PR, and I thought once everything will be done, I will mark it as a PR instead of draft pr.

Fix patch:
diff --git a/README.md b/README.md
index bb9a7dc9ad..9b756d79f7 100644
--- a/README.md
+++ b/README.md
@@ -72,20 +72,21 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
 | [Internet.bs](https://go-acme.github.io/lego/dns/internetbs/)                   | [INWX](https://go-acme.github.io/lego/dns/inwx/)                                | [Ionos](https://go-acme.github.io/lego/dns/ionos/)                              | [IPv64](https://go-acme.github.io/lego/dns/ipv64/)                              |
 | [iwantmyname](https://go-acme.github.io/lego/dns/iwantmyname/)                  | [Joker](https://go-acme.github.io/lego/dns/joker/)                              | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/)               | [Liara](https://go-acme.github.io/lego/dns/liara/)                              |
 | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/)                       | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/)                     | [Loopia](https://go-acme.github.io/lego/dns/loopia/)                            | [LuaDNS](https://go-acme.github.io/lego/dns/luadns/)                            |
-| [Mail-in-a-Box](https://go-acme.github.io/lego/dns/mailinabox/)                 | [Manual](https://go-acme.github.io/lego/dns/manual/)                            | [Metaname](https://go-acme.github.io/lego/dns/metaname/)                        | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/)                         |
-| [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/)                | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/)                      | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/)                      | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/)                        |
-| [NearlyFreeSpeech.NET](https://go-acme.github.io/lego/dns/nearlyfreespeech/)    | [Netcup](https://go-acme.github.io/lego/dns/netcup/)                            | [Netlify](https://go-acme.github.io/lego/dns/netlify/)                          | [Nicmanager](https://go-acme.github.io/lego/dns/nicmanager/)                    |
-| [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/)                        | [Njalla](https://go-acme.github.io/lego/dns/njalla/)                            | [Nodion](https://go-acme.github.io/lego/dns/nodion/)                            | [NS1](https://go-acme.github.io/lego/dns/ns1/)                                  |
-| [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/)                   | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/)                 | [OVH](https://go-acme.github.io/lego/dns/ovh/)                                  | [plesk.com](https://go-acme.github.io/lego/dns/plesk/)                          |
-| [Porkbun](https://go-acme.github.io/lego/dns/porkbun/)                          | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/)                            | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/)                      | [RcodeZero](https://go-acme.github.io/lego/dns/rcodezero/)                      |
-| [reg.ru](https://go-acme.github.io/lego/dns/regru/)                             | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/)                          | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/)                  | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/)                 |
-| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/)                        | [Selectel v2](https://go-acme.github.io/lego/dns/selectelv2/)                   | [Selectel](https://go-acme.github.io/lego/dns/selectel/)                        | [Servercow](https://go-acme.github.io/lego/dns/servercow/)                      |
-| [Shellrent](https://go-acme.github.io/lego/dns/shellrent/)                      | [Simply.com](https://go-acme.github.io/lego/dns/simply/)                        | [Sonic](https://go-acme.github.io/lego/dns/sonic/)                              | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/)                      |
-| [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/)           | [TransIP](https://go-acme.github.io/lego/dns/transip/)                          | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/)                   | [Ultradns](https://go-acme.github.io/lego/dns/ultradns/)                        |
-| [Variomedia](https://go-acme.github.io/lego/dns/variomedia/)                    | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/)                          | [Vercel](https://go-acme.github.io/lego/dns/vercel/)                            | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/)                 |
-| [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/)                        | [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/)                         | [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            | [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              |
-| [Webnames](https://go-acme.github.io/lego/dns/webnames/)                        | [Websupport](https://go-acme.github.io/lego/dns/websupport/)                    | [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              | [Yandex 360](https://go-acme.github.io/lego/dns/yandex360/)                     |
-| [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/)                 | [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/)                        | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/)                           | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/)                            |
+| [Mail-in-a-Box](https://go-acme.github.io/lego/dns/mailinabox/)                 | [Manual](https://go-acme.github.io/lego/dns/manual/)                            | [Metaname](https://go-acme.github.io/lego/dns/metaname/)                        | [mijn.host](https://go-acme.github.io/lego/dns/mijnhost/)                       |
+| [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/)                         | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/)                | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/)                      | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/)                      |
+| [Namesilo](https://go-acme.github.io/lego/dns/namesilo/)                        | [NearlyFreeSpeech.NET](https://go-acme.github.io/lego/dns/nearlyfreespeech/)    | [Netcup](https://go-acme.github.io/lego/dns/netcup/)                            | [Netlify](https://go-acme.github.io/lego/dns/netlify/)                          |
+| [Nicmanager](https://go-acme.github.io/lego/dns/nicmanager/)                    | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/)                        | [Njalla](https://go-acme.github.io/lego/dns/njalla/)                            | [Nodion](https://go-acme.github.io/lego/dns/nodion/)                            |
+| [NS1](https://go-acme.github.io/lego/dns/ns1/)                                  | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/)                   | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/)                 | [OVH](https://go-acme.github.io/lego/dns/ovh/)                                  |
+| [plesk.com](https://go-acme.github.io/lego/dns/plesk/)                          | [Porkbun](https://go-acme.github.io/lego/dns/porkbun/)                          | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/)                            | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/)                      |
+| [RcodeZero](https://go-acme.github.io/lego/dns/rcodezero/)                      | [reg.ru](https://go-acme.github.io/lego/dns/regru/)                             | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/)                          | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/)                  |
+| [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/)                 | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/)                        | [Selectel v2](https://go-acme.github.io/lego/dns/selectelv2/)                   | [Selectel](https://go-acme.github.io/lego/dns/selectel/)                        |
+| [Servercow](https://go-acme.github.io/lego/dns/servercow/)                      | [Shellrent](https://go-acme.github.io/lego/dns/shellrent/)                      | [Simply.com](https://go-acme.github.io/lego/dns/simply/)                        | [Sonic](https://go-acme.github.io/lego/dns/sonic/)                              |
+| [Stackpath](https://go-acme.github.io/lego/dns/stackpath/)                      | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/)           | [TransIP](https://go-acme.github.io/lego/dns/transip/)                          | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/)                   |
+| [Ultradns](https://go-acme.github.io/lego/dns/ultradns/)                        | [Variomedia](https://go-acme.github.io/lego/dns/variomedia/)                    | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/)                          | [Vercel](https://go-acme.github.io/lego/dns/vercel/)                            |
+| [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/)                 | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/)                        | [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/)                         | [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            |
+| [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              | [Webnames](https://go-acme.github.io/lego/dns/webnames/)                        | [Websupport](https://go-acme.github.io/lego/dns/websupport/)                    | [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              |
+| [Yandex 360](https://go-acme.github.io/lego/dns/yandex360/)                     | [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/)                 | [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/)                        | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/)                           |
+| [Zonomi](https://go-acme.github.io/lego/dns/zonomi/)                            |                                                                                 |                                                                                 |                                                                                 |
 
 <!-- END DNS PROVIDERS LIST -->
 
diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go
index eea897e7fc..34b36be968 100644
--- a/cmd/zz_gen_cmd_dnshelp.go
+++ b/cmd/zz_gen_cmd_dnshelp.go
@@ -91,6 +91,7 @@ func allDNSCodes() string {
 		"luadns",
 		"mailinabox",
 		"metaname",
+		"mijnhost",
 		"mydnsjp",
 		"mythicbeasts",
 		"namecheap",
@@ -1791,6 +1792,27 @@ func displayDNSHelp(w io.Writer, name string) error {
 		ew.writeln()
 		ew.writeln(`More information: https://go-acme.github.io/lego/dns/metaname`)
 
+	case "mijnhost":
+		// generated from: providers/dns/mijnhost/mijnhost.toml
+		ew.writeln(`Configuration for mijn.host.`)
+		ew.writeln(`Code:	'mijnhost'`)
+		ew.writeln(`Since:	'v4.18.0'`)
+		ew.writeln()
+
+		ew.writeln(`Credentials:`)
+		ew.writeln(`	- "MIJNHOST_API_KEY":	The API key`)
+		ew.writeln()
+
+		ew.writeln(`Additional Configuration:`)
+		ew.writeln(`	- "MIJNHOST_HTTP_TIMEOUT":	API request timeout`)
+		ew.writeln(`	- "MIJNHOST_POLLING_INTERVAL":	Time between DNS propagation check`)
+		ew.writeln(`	- "MIJNHOST_PROPAGATION_TIMEOUT":	Maximum waiting time for DNS propagation`)
+		ew.writeln(`	- "MIJNHOST_SEQUENCE_INTERVAL":	Time between sequential requests`)
+		ew.writeln(`	- "MIJNHOST_TTL":	The TTL of the TXT record used for the DNS challenge`)
+
+		ew.writeln()
+		ew.writeln(`More information: https://go-acme.github.io/lego/dns/mijnhost`)
+
 	case "mydnsjp":
 		// generated from: providers/dns/mydnsjp/mydnsjp.toml
 		ew.writeln(`Configuration for MyDNS.jp.`)
diff --git a/docs/content/dns/zz_gen_mijnhost.md b/docs/content/dns/zz_gen_mijnhost.md
new file mode 100644
index 0000000000..cd1dc720d5
--- /dev/null
+++ b/docs/content/dns/zz_gen_mijnhost.md
@@ -0,0 +1,68 @@
+---
+title: "mijn.host"
+date: 2019-03-03T16:39:46+01:00
+draft: false
+slug: mijnhost
+dnsprovider:
+  since:    "v4.18.0"
+  code:     "mijnhost"
+  url:      "https://mijn.host/"
+---
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/mijnhost/mijnhost.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+
+
+Configuration for [mijn.host](https://mijn.host/).
+
+
+<!--more-->
+
+- Code: `mijnhost`
+- Since: v4.18.0
+
+
+Here is an example bash command using the mijn.host provider:
+
+```bash
+MIJNHOST_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \
+lego --email myemail@example.com --dns mijnhost --domains my.example.org run
+```
+
+
+
+
+## Credentials
+
+| Environment Variable Name | Description |
+|-----------------------|-------------|
+| `MIJNHOST_API_KEY` | The API key |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here]({{% ref "dns#configuration-and-credentials" %}}).
+
+
+## Additional Configuration
+
+| Environment Variable Name | Description |
+|--------------------------------|-------------|
+| `MIJNHOST_HTTP_TIMEOUT` | API request timeout |
+| `MIJNHOST_POLLING_INTERVAL` | Time between DNS propagation check |
+| `MIJNHOST_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
+| `MIJNHOST_SEQUENCE_INTERVAL` | Time between sequential requests |
+| `MIJNHOST_TTL` | The TTL of the TXT record used for the DNS challenge |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here]({{% ref "dns#configuration-and-credentials" %}}).
+
+
+
+
+## More information
+
+- [API documentation](https://mijn.host/api/doc/)
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/mijnhost/mijnhost.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml
index edf7d61861..b237cf7bc0 100644
--- a/docs/data/zz_cli_help.toml
+++ b/docs/data/zz_cli_help.toml
@@ -138,7 +138,7 @@ To display the documentation for a specific DNS provider, run:
   $ lego dnshelp -c code
 
 Supported DNS providers:
-  acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, mailinabox, manual, metaname, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, servercow, shellrent, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
+  acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, mailinabox, manual, metaname, mijnhost, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, servercow, shellrent, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
 
 More information: https://go-acme.github.io/lego/dns
 """
diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go
index 36a47772ba..52e4fc94a9 100644
--- a/providers/dns/dns_providers.go
+++ b/providers/dns/dns_providers.go
@@ -82,6 +82,7 @@ import (
 	"github.com/go-acme/lego/v4/providers/dns/luadns"
 	"github.com/go-acme/lego/v4/providers/dns/mailinabox"
 	"github.com/go-acme/lego/v4/providers/dns/metaname"
+	"github.com/go-acme/lego/v4/providers/dns/mijnhost"
 	"github.com/go-acme/lego/v4/providers/dns/mydnsjp"
 	"github.com/go-acme/lego/v4/providers/dns/mythicbeasts"
 	"github.com/go-acme/lego/v4/providers/dns/namecheap"
@@ -297,6 +298,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
 		return dns01.NewDNSProviderManual()
 	case "metaname":
 		return metaname.NewDNSProvider()
+	case "mijnhost":
+		return mijnhost.NewDNSProvider()
 	case "mydnsjp":
 		return mydnsjp.NewDNSProvider()
 	case "mythicbeasts":
diff --git a/providers/dns/mijnhost/internal/client.go b/providers/dns/mijnhost/internal/client.go
new file mode 100644
index 0000000000..82bdcfeb93
--- /dev/null
+++ b/providers/dns/mijnhost/internal/client.go
@@ -0,0 +1,160 @@
+package internal
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"time"
+
+	"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
+)
+
+const defaultBaseURL = "https://mijn.host/api/v2/"
+
+const authorizationHeader = "API-Key"
+
+// Client a mijn.host DNS API client.
+type Client struct {
+	apiKey string
+
+	baseURL    *url.URL
+	HTTPClient *http.Client
+}
+
+// NewClient creates a new Client.
+func NewClient(apiKey string) *Client {
+	baseURL, _ := url.Parse(defaultBaseURL)
+
+	return &Client{
+		apiKey:     apiKey,
+		baseURL:    baseURL,
+		HTTPClient: &http.Client{Timeout: 10 * time.Second},
+	}
+}
+
+// ListDomains Retrieve all domains from an account.
+// https://mijn.host/api/doc/api-3563872
+func (c Client) ListDomains(ctx context.Context) ([]Domain, error) {
+	endpoint := c.baseURL.JoinPath("domains")
+
+	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
+	if err != nil {
+		return nil, fmt.Errorf("create request: %w", err)
+	}
+
+	var results Response[DomainData]
+	err = c.do(req, &results)
+	if err != nil {
+		return nil, err
+	}
+
+	return results.Data.Domains, nil
+}
+
+// GetRecords Retrieve DNS records of specific domain.
+// https://mijn.host/api/doc/api-3563906
+func (c Client) GetRecords(ctx context.Context, domain string) ([]Record, error) {
+	endpoint := c.baseURL.JoinPath("domains", domain, "dns")
+
+	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
+	if err != nil {
+		return nil, fmt.Errorf("create request: %w", err)
+	}
+
+	var results Response[RecordData]
+	err = c.do(req, &results)
+	if err != nil {
+		return nil, err
+	}
+
+	return results.Data.Records, nil
+}
+
+// UpdateRecords Update DNS records of specific domain.
+// https://mijn.host/api/doc/api-3563907
+func (c Client) UpdateRecords(ctx context.Context, domain string, records []Record) error {
+	endpoint := c.baseURL.JoinPath("domains", domain, "dns")
+
+	req, err := newJSONRequest(ctx, http.MethodPut, endpoint, RecordData{Records: records})
+	if err != nil {
+		return fmt.Errorf("create request: %w", err)
+	}
+
+	err = c.do(req, nil)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (c Client) do(req *http.Request, result any) error {
+	req.Header.Set(authorizationHeader, c.apiKey)
+
+	resp, err := c.HTTPClient.Do(req)
+	if err != nil {
+		return errutils.NewHTTPDoError(req, err)
+	}
+
+	defer func() { _ = resp.Body.Close() }()
+
+	if resp.StatusCode != http.StatusOK {
+		return parseError(req, resp)
+	}
+
+	if result == nil {
+		return nil
+	}
+
+	raw, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return errutils.NewReadResponseError(req, resp.StatusCode, err)
+	}
+
+	err = json.Unmarshal(raw, result)
+	if err != nil {
+		return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
+	}
+
+	return nil
+}
+
+func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
+	buf := new(bytes.Buffer)
+
+	if payload != nil {
+		err := json.NewEncoder(buf).Encode(payload)
+		if err != nil {
+			return nil, fmt.Errorf("failed to create request JSON body: %w", err)
+		}
+	}
+
+	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
+	if err != nil {
+		return nil, fmt.Errorf("unable to create request: %w", err)
+	}
+
+	req.Header.Set("Accept", "application/json")
+
+	if payload != nil {
+		req.Header.Set("Content-Type", "application/json")
+	}
+
+	return req, nil
+}
+
+func parseError(req *http.Request, resp *http.Response) error {
+	raw, _ := io.ReadAll(resp.Body)
+
+	var errAPI APIError
+	err := json.Unmarshal(raw, &errAPI)
+	if err != nil {
+		return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
+	}
+
+	return &errAPI
+}
diff --git a/providers/dns/mijnhost/internal/fixtures/error.json b/providers/dns/mijnhost/internal/fixtures/error.json
new file mode 100644
index 0000000000..fb7423a1da
--- /dev/null
+++ b/providers/dns/mijnhost/internal/fixtures/error.json
@@ -0,0 +1,4 @@
+{
+  "status": 400,
+  "status_description": "Wrong request method"
+}
diff --git a/providers/dns/mijnhost/internal/fixtures/get-dns-records.json b/providers/dns/mijnhost/internal/fixtures/get-dns-records.json
new file mode 100644
index 0000000000..22db65fc60
--- /dev/null
+++ b/providers/dns/mijnhost/internal/fixtures/get-dns-records.json
@@ -0,0 +1,33 @@
+{
+  "status": 200,
+  "status_description": "Request successful",
+  "data": {
+    "domain": "example.com",
+    "records": [
+      {
+        "type": "A",
+        "name": "example.com.",
+        "value": "135.226.123.12",
+        "ttl": 900
+      },
+      {
+        "type": "AAAA",
+        "name": "example.com.",
+        "value": "2009:21d0:322:6100::5:c92b",
+        "ttl": 900
+      },
+      {
+        "type": "MX",
+        "name": "example.com.",
+        "value": "10 mail.example.com.",
+        "ttl": 900
+      },
+      {
+        "type": "TXT",
+        "name": "example.com.",
+        "value": "v=spf1 include:spf.mijn.host ~all",
+        "ttl": 900
+      }
+    ]
+  }
+}
diff --git a/providers/dns/mijnhost/internal/fixtures/list-domains.json b/providers/dns/mijnhost/internal/fixtures/list-domains.json
new file mode 100644
index 0000000000..b87b00668a
--- /dev/null
+++ b/providers/dns/mijnhost/internal/fixtures/list-domains.json
@@ -0,0 +1,18 @@
+{
+  "status": 200,
+  "status_description": "Request successful",
+  "data": {
+    "domains": [
+      {
+        "id": 1000,
+        "domain": "example.com",
+        "renewal_date": "2030-01-01",
+        "status": "Active",
+        "status_id": 1,
+        "tags": [
+          "my-tag"
+        ]
+      }
+    ]
+  }
+}
diff --git a/providers/dns/mijnhost/internal/fixtures/update-dns-records.json b/providers/dns/mijnhost/internal/fixtures/update-dns-records.json
new file mode 100644
index 0000000000..02155feaf0
--- /dev/null
+++ b/providers/dns/mijnhost/internal/fixtures/update-dns-records.json
@@ -0,0 +1,4 @@
+{
+  "status": 200,
+  "status_description": "DNS successfully updated"
+}
diff --git a/providers/dns/mijnhost/internal/types.go b/providers/dns/mijnhost/internal/types.go
new file mode 100644
index 0000000000..aef3c33a4c
--- /dev/null
+++ b/providers/dns/mijnhost/internal/types.go
@@ -0,0 +1,43 @@
+package internal
+
+import "fmt"
+
+type APIError struct {
+	Status            int    `json:"status,omitempty"`
+	StatusDescription string `json:"status_description,omitempty"`
+}
+
+func (e APIError) Error() string {
+	return fmt.Sprintf("%d: %s", e.Status, e.StatusDescription)
+}
+
+type Response[T any] struct {
+	Status            int    `json:"status,omitempty"`
+	StatusDescription string `json:"status_description,omitempty"`
+	Data              T      `json:"data,omitempty"`
+}
+
+type RecordData struct {
+	Domain  string   `json:"domain,omitempty"`
+	Records []Record `json:"records,omitempty"`
+}
+
+type Record struct {
+	Type  string `json:"type,omitempty"`
+	Name  string `json:"name,omitempty"`
+	Value string `json:"value,omitempty"`
+	TTL   int    `json:"ttl,omitempty"`
+}
+
+type DomainData struct {
+	Domains []Domain `json:"domains"`
+}
+
+type Domain struct {
+	ID          int      `json:"id"`
+	Domain      string   `json:"domain"`
+	RenewalDate string   `json:"renewal_date"`
+	Status      string   `json:"status"`
+	StatusID    int      `json:"status_id"`
+	Tags        []string `json:"tags"`
+}
diff --git a/providers/dns/mijnhost/mijnhost.go b/providers/dns/mijnhost/mijnhost.go
new file mode 100644
index 0000000000..4d2cc1b39a
--- /dev/null
+++ b/providers/dns/mijnhost/mijnhost.go
@@ -0,0 +1,209 @@
+// Package mijnhost implements a DNS provider for solving the DNS-01 challenge using mijn.host DNS.
+package mijnhost
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/go-acme/lego/v4/challenge/dns01"
+	"github.com/go-acme/lego/v4/platform/config/env"
+	"github.com/go-acme/lego/v4/providers/dns/mijnhost/internal"
+	"github.com/miekg/dns"
+)
+
+// Environment variables names.
+const (
+	envNamespace = "MIJNHOST_"
+
+	EnvAPIKey = envNamespace + "API_KEY"
+
+	EnvTTL                = envNamespace + "TTL"
+	EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
+	EnvPollingInterval    = envNamespace + "POLLING_INTERVAL"
+	EnvSequenceInterval   = envNamespace + "SEQUENCE_INTERVAL"
+	EnvHTTPTimeout        = envNamespace + "HTTP_TIMEOUT"
+)
+
+// Config is used to configure the creation of the DNSProvider.
+type Config struct {
+	APIKey             string
+	TTL                int
+	PropagationTimeout time.Duration
+	PollingInterval    time.Duration
+	SequenceInterval   time.Duration
+	HTTPClient         *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider.
+func NewDefaultConfig() *Config {
+	return &Config{
+		TTL:                env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
+		PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
+		PollingInterval:    env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
+		SequenceInterval:   env.GetOrDefaultSecond(EnvSequenceInterval, 5*time.Second),
+		HTTPClient: &http.Client{
+			Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
+		},
+	}
+}
+
+// DNSProvider implements the challenge.Provider interface.
+type DNSProvider struct {
+	config *Config
+	client *internal.Client
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for mijn.host DNS.
+// MIJNHOST_API_KEY must be passed in the environment variables.
+func NewDNSProvider() (*DNSProvider, error) {
+	values, err := env.Get(EnvAPIKey)
+	if err != nil {
+		return nil, fmt.Errorf("mijnhost: %w", err)
+	}
+
+	config := NewDefaultConfig()
+	config.APIKey = values[EnvAPIKey]
+
+	return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for mijn.host DNS.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+	if config == nil {
+		return nil, errors.New("mijnhost: the configuration of the DNS provider is nil")
+	}
+
+	if config.APIKey == "" {
+		return nil, errors.New("mijnhost: APIKey is missing")
+	}
+
+	client := internal.NewClient(config.APIKey)
+
+	return &DNSProvider{
+		config: config,
+		client: client,
+	}, nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+	return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+// Sequential All DNS challenges for this provider will be resolved sequentially.
+// Returns the interval between each iteration.
+func (d *DNSProvider) Sequential() time.Duration {
+	return d.config.SequenceInterval
+}
+
+// Present creates a TXT record to fulfill the dns-01 challenge.
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+	info := dns01.GetChallengeInfo(domain, keyAuth)
+
+	domains, err := d.client.ListDomains(context.Background())
+	if err != nil {
+		return fmt.Errorf("mijnhost: list domains: %w", err)
+	}
+
+	dom, err := findDomain(domains, domain)
+	if err != nil {
+		return fmt.Errorf("mijnhost: find domain: %w", err)
+	}
+
+	records, err := d.client.GetRecords(context.Background(), dom.Domain)
+	if err != nil {
+		return fmt.Errorf("mijnhost: get records: %w", err)
+	}
+
+	subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, dom.Domain)
+	if err != nil {
+		return fmt.Errorf("mijnhost: %w", err)
+	}
+
+	record := internal.Record{
+		Type:  "TXT",
+		Name:  subDomain,
+		Value: info.Value,
+		TTL:   d.config.TTL,
+	}
+
+	// mijn.host doesn't support multiple values for a domain,
+	// so we removed existing record for the subdomain.
+	cleanedRecords := filterRecords(records, func(record internal.Record) bool {
+		return record.Name == subDomain || record.Name == dns01.UnFqdn(info.EffectiveFQDN)
+	})
+
+	cleanedRecords = append(cleanedRecords, record)
+
+	err = d.client.UpdateRecords(context.Background(), dom.Domain, cleanedRecords)
+	if err != nil {
+		return fmt.Errorf("mijnhost: update records: %w", err)
+	}
+
+	return nil
+}
+
+// CleanUp removes the TXT record.
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+	info := dns01.GetChallengeInfo(domain, keyAuth)
+
+	domains, err := d.client.ListDomains(context.Background())
+	if err != nil {
+		return fmt.Errorf("mijnhost: list domains: %w", err)
+	}
+
+	dom, err := findDomain(domains, domain)
+	if err != nil {
+		return fmt.Errorf("mijnhost: find domain: %w", err)
+	}
+
+	records, err := d.client.GetRecords(context.Background(), dom.Domain)
+	if err != nil {
+		return fmt.Errorf("mijnhost: get records: %w", err)
+	}
+
+	cleanedRecords := filterRecords(records, func(record internal.Record) bool {
+		return record.Value == info.Value
+	})
+
+	err = d.client.UpdateRecords(context.Background(), dom.Domain, cleanedRecords)
+	if err != nil {
+		return fmt.Errorf("mijnhost: update records: %w", err)
+	}
+
+	return nil
+}
+
+func findDomain(domains []internal.Domain, fqdn string) (internal.Domain, error) {
+	labelIndexes := dns.Split(fqdn)
+
+	for _, index := range labelIndexes {
+		domain := dns01.UnFqdn(fqdn[index:])
+
+		for _, dom := range domains {
+			if dom.Domain == domain {
+				return dom, nil
+			}
+		}
+	}
+
+	return internal.Domain{}, fmt.Errorf("domain %s not found", fqdn)
+}
+
+func filterRecords(records []internal.Record, fn func(record internal.Record) bool) []internal.Record {
+	var newRecords []internal.Record
+
+	for _, record := range records {
+		if record.Type == "TXT" && fn(record) {
+			continue
+		}
+
+		newRecords = append(newRecords, record)
+	}
+
+	return newRecords
+}
diff --git a/providers/dns/mijnhost/mijnhost.toml b/providers/dns/mijnhost/mijnhost.toml
new file mode 100644
index 0000000000..7140c45f54
--- /dev/null
+++ b/providers/dns/mijnhost/mijnhost.toml
@@ -0,0 +1,23 @@
+Name = "mijn.host"
+Description = ''''''
+URL = "https://mijn.host/"
+Code = "mijnhost"
+Since = "v4.18.0"
+
+Example = '''
+MIJNHOST_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \
+lego --email myemail@example.com --dns mijnhost --domains my.example.org run
+'''
+
+[Configuration]
+  [Configuration.Credentials]
+    MIJNHOST_API_KEY = "The API key"
+  [Configuration.Additional]
+    MIJNHOST_POLLING_INTERVAL = "Time between DNS propagation check"
+    MIJNHOST_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
+    MIJNHOST_SEQUENCE_INTERVAL = "Time between sequential requests"
+    MIJNHOST_TTL = "The TTL of the TXT record used for the DNS challenge"
+    MIJNHOST_HTTP_TIMEOUT = "API request timeout"
+
+[Links]
+  API = "https://mijn.host/api/doc/"


Test patch:
diff --git a/providers/dns/mijnhost/internal/client_test.go b/providers/dns/mijnhost/internal/client_test.go
new file mode 100644
index 0000000000..876ca5e1cb
--- /dev/null
+++ b/providers/dns/mijnhost/internal/client_test.go
@@ -0,0 +1,129 @@
+package internal
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+const apiKey = "secret"
+
+func setupTest(t *testing.T) (*Client, *http.ServeMux) {
+	t.Helper()
+
+	mux := http.NewServeMux()
+	server := httptest.NewServer(mux)
+	t.Cleanup(server.Close)
+
+	client := NewClient(apiKey)
+	client.baseURL, _ = url.Parse(server.URL)
+
+	return client, mux
+}
+
+func testHandler(filename string, method string, statusCode int) http.HandlerFunc {
+	return func(rw http.ResponseWriter, req *http.Request) {
+		if req.Method != method {
+			http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed)
+			return
+		}
+
+		auth := req.Header.Get(authorizationHeader)
+		if auth != apiKey {
+			http.Error(rw, "invalid Authorization header", http.StatusUnauthorized)
+			return
+		}
+
+		file, err := os.Open(filepath.Join("fixtures", filename))
+		if err != nil {
+			http.Error(rw, err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		defer func() { _ = file.Close() }()
+
+		rw.WriteHeader(statusCode)
+
+		_, err = io.Copy(rw, file)
+		if err != nil {
+			http.Error(rw, err.Error(), http.StatusInternalServerError)
+			return
+		}
+	}
+}
+
+func TestClient_ListDomains(t *testing.T) {
+	client, mux := setupTest(t)
+
+	mux.HandleFunc("/domains", testHandler("./list-domains.json", http.MethodGet, http.StatusOK))
+
+	domains, err := client.ListDomains(context.Background())
+	require.NoError(t, err)
+
+	expected := []Domain{{
+		ID:          1000,
+		Domain:      "example.com",
+		RenewalDate: "2030-01-01",
+		Status:      "Active",
+		StatusID:    1,
+		Tags:        []string{"my-tag"},
+	}}
+
+	assert.Equal(t, expected, domains)
+}
+
+func TestClient_GetRecords(t *testing.T) {
+	client, mux := setupTest(t)
+
+	mux.HandleFunc("/domains/example.com/dns", testHandler("./get-dns-records.json", http.MethodGet, http.StatusOK))
+
+	records, err := client.GetRecords(context.Background(), "example.com")
+	require.NoError(t, err)
+
+	expected := []Record{
+		{
+			Type:  "A",
+			Name:  "example.com.",
+			Value: "135.226.123.12",
+			TTL:   900,
+		},
+		{
+			Type:  "AAAA",
+			Name:  "example.com.",
+			Value: "2009:21d0:322:6100::5:c92b",
+			TTL:   900,
+		},
+		{
+			Type:  "MX",
+			Name:  "example.com.",
+			Value: "10 mail.example.com.",
+			TTL:   900,
+		},
+		{
+			Type:  "TXT",
+			Name:  "example.com.",
+			Value: "v=spf1 include:spf.mijn.host ~all",
+			TTL:   900,
+		},
+	}
+
+	assert.Equal(t, expected, records)
+}
+
+func TestClient_UpdateRecords(t *testing.T) {
+	client, mux := setupTest(t)
+
+	mux.HandleFunc("/domains/example.com/dns", testHandler("./update-dns-records.json", http.MethodPut, http.StatusOK))
+
+	err := client.UpdateRecords(context.Background(), "example.com", nil)
+	require.NoError(t, err)
+}
diff --git a/providers/dns/mijnhost/mijnhost_test.go b/providers/dns/mijnhost/mijnhost_test.go
new file mode 100644
index 0000000000..a48f84ca8f
--- /dev/null
+++ b/providers/dns/mijnhost/mijnhost_test.go
@@ -0,0 +1,115 @@
+package mijnhost
+
+import (
+	"testing"
+
+	"github.com/go-acme/lego/v4/platform/tester"
+	"github.com/stretchr/testify/require"
+)
+
+const envDomain = envNamespace + "DOMAIN"
+
+var envTest = tester.NewEnvTest(EnvAPIKey).WithDomain(envDomain)
+
+func TestNewDNSProvider(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		envVars  map[string]string
+		expected string
+	}{
+		{
+			desc: "success",
+			envVars: map[string]string{
+				EnvAPIKey: "key",
+			},
+		},
+		{
+			desc:     "missing API key",
+			envVars:  map[string]string{},
+			expected: "mijnhost: some credentials information are missing: MIJNHOST_API_KEY",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			defer envTest.RestoreEnv()
+			envTest.ClearEnv()
+
+			envTest.Apply(test.envVars)
+
+			p, err := NewDNSProvider()
+
+			if test.expected == "" {
+				require.NoError(t, err)
+				require.NotNil(t, p)
+				require.NotNil(t, p.config)
+				require.NotNil(t, p.client)
+			} else {
+				require.EqualError(t, err, test.expected)
+			}
+		})
+	}
+}
+
+func TestNewDNSProviderConfig(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		apiKey   string
+		ttl      int
+		expected string
+	}{
+		{
+			desc:   "success",
+			apiKey: "key",
+		},
+		{
+			desc:     "missing API key",
+			expected: "mijnhost: APIKey is missing",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			config := NewDefaultConfig()
+			config.APIKey = test.apiKey
+			config.TTL = test.ttl
+
+			p, err := NewDNSProviderConfig(config)
+
+			if test.expected == "" {
+				require.NoError(t, err)
+				require.NotNil(t, p)
+				require.NotNil(t, p.config)
+				require.NotNil(t, p.client)
+			} else {
+				require.EqualError(t, err, test.expected)
+			}
+		})
+	}
+}
+
+func TestLivePresent(t *testing.T) {
+	if !envTest.IsLiveTest() {
+		t.Skip("skipping live test")
+	}
+
+	envTest.RestoreEnv()
+	provider, err := NewDNSProvider()
+	require.NoError(t, err)
+
+	err = provider.Present(envTest.GetDomain(), "", "123d==")
+	require.NoError(t, err)
+}
+
+func TestLiveCleanUp(t *testing.T) {
+	if !envTest.IsLiveTest() {
+		t.Skip("skipping live test")
+	}
+
+	envTest.RestoreEnv()
+	provider, err := NewDNSProvider()
+	require.NoError(t, err)
+
+	err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
+	require.NoError(t, err)
+}