A Terraform provider in an hour
There is no Terraform provider for SMTP2GO, so I had Claude write one: sender domains, SMTP users and API keys, for about an hour of my time. Most of the work was finding out what the API does that its documentation does not say.
In 2025, faced with a service that had no Terraform provider, I wrapped a Python script in Terraform and lived with the result for a year. In October 2026 I hit the same gap with SMTP2GO, the relay my servers and monitoring use to send email, and this time I had Claude write the provider instead. It took about an hour of my time.
The reason was small and practical. My DNS lives in Terraform, and the records SMTP2GO needs for a sender domain — DKIM, return path, link tracking — were hardcoded in it, copied by hand. With a provider, the sender domain becomes a resource whose outputs feed the DNS records directly, and a verification resource waits until SMTP2GO confirms them. It also manages SMTP users and API keys. The provider is unpublished: it builds into a local plugin mirror on each machine that runs Terraform, which is enough for one person.
Writing it
The provider uses HashiCorp’s Plugin Framework in Go. Generating it from SMTP2GO’s OpenAPI description was the obvious shortcut and did not survive a first reading: every endpoint is a POST with a JSON body, and the schemas contradict the examples next to them. The first pass produced the client, the four resources, and an in-memory fake of the API to test them against. The rest went to running acceptance tests against the real API, and most of the commits from then on correct something it did differently from the documentation.
What the API does that the documentation does not say
The worst one was SMTP users. Editing a user without sending its password does not leave the password alone; it generates a new one, which the response does not include and no later call returns, so the old password stops working and the new one is lost. The PATCH variant, documented as ignoring missing properties, does exactly the same. The provider therefore refuses to update an SMTP user whose password it does not know, such as one that was imported, until a password is set in the configuration. That is an inconvenient error message, and the alternative was silently breaking every machine that relays mail through that user.
The rest were smaller. API keys are shown in full only once, in the response that creates them, so the provider keeps them in state and importing one needs the full key. Renaming a tracking or return-path subdomain only works with bare labels like link, never the full hostname, which every other part of the API uses. Verification returns success even when the records are missing, with the lookup errors buried in status fields, so the verification resource polls those fields until they are all clean or the timeout expires. And creating API keys is limited to five a minute, which an acceptance test run reaches quickly.
All of it is written down in the repository’s API notes, since the next person to find out would otherwise be me.
Was it worth it?
Yes. The SMTP2GO records in my DNS now come from the sender domains themselves instead of being copied by hand, and an hour is a small price for a provider that refuses to break mail delivery in the one place where the API would have let it. A year earlier, the same gap cost me a Python script I was still apologising for in a blog post.
Unpublished — Go, Terraform Plugin Framework, SMTP2GO