`pkg/contactform` is the public, reusable Go package that the nuntius HTTP server uses for validation. It has no third-party dependencies and no awareness of HTTP, SMTP, or the filesystem — drop it into any Go project that needs to validate a contact-style form payload.
| `Honeypot` | `-` | Never marshalled to / from JSON. Read it from a separate field in your own request struct, or wire it through your favourite JSON tag. |
The handler-side mapping (used inside the nuntius server) reads the honeypot value into `Request.Honeypot` based on the form's `honeypot_field` config. If you embed `contactform.Request` directly in your own API, you are responsible for reading the honeypot field separately.
### `Response`
```go
typeResponsestruct{
OKbool`json:"ok"`
}
```
A successful submission returns `{"ok": true}`.
### `FieldError`
```go
typeFieldErrorstruct{
Fieldstring`json:"field"`
Messagestring`json:"message"`
}
```
One entry in the `details[]` array of a `validation` error.
### `ErrorResponse`
```go
typeErrorResponsestruct{
Errorstring`json:"error"`
Messagestring`json:"message,omitempty"`
Details[]FieldError`json:"details,omitempty"`
}
```
Returned for any non-2xx response. The `Error` field is a short, machine-readable code (`validation`, `invalid_json`, `origin_not_allowed`, `rate_limited`, `send_failed`, `storage_failed`). `Message` is a human-readable summary; `Details` carries the per-field validation failures.
| `"feedback"` | `name`, `email`, `message` (no `service`) |
| `"newsletter"` | `email` only |
| `"generic"` | `name`, `email`, `message` (no `service`) |
| `""` (empty) | Falls back to `contact` |
| anything else | Falls back to `contact` — the strict allow-list is enforced at the config layer, not here |
Whitespace is trimmed from `name`, `email`, `service`, and `message`**before** validation. The trimmed values are what the validator sees and what the email body sees.
### Constants
| Name | Value | Used by |
|---|---|---|
| `MinNameRunes` | `2` | `name` minimum length |
| `MaxNameRunes` | `100` | `name` maximum length |
If you need to extend the allow-list of `service` values, edit `validServices` in `pkg/contactform/validate.go`:
```go
varvalidServices=map[string]bool{
"":true,
"architecture":true,
"ai":true,
"infrastructure":true,
"software":true,
"unix":true,
"other":true,
// add yours here, e.g.:
// "security": true,
}
```
If you need different length limits, edit the four `Min*` / `Max*` constants in the same file. Or fork the package — it is intentionally small (~150 LOC) so a copy-into-your-project is also reasonable.
The example deliberately omits CORS, rate limiting, and the honeypot — copy those from `internal/handler/contact.go` if you need them.
## Why a Separate Package?
`pkg/contactform` is the one piece of nuntius that is **not** tied to HTTP, SMTP, or the filesystem. Keeping it in a separate, dependency-free package:
- Makes it trivially embeddable in any Go service.
- Makes it cheap to unit-test (see `pkg/contactform/validate_test.go`).
- Makes the public API obvious — there is one entry point (`Validate`) and four types, all in one file pair.
- Keeps `internal/handler` focused on HTTP, `internal/email` on SMTP, and `internal/storage` on disk.