Skip to content

feat: bootstrap contactkit monorepo#2

Merged
dimeloper merged 5 commits into
mainfrom
copilot/implement-contactkit-server-requirements
May 6, 2026
Merged

feat: bootstrap contactkit monorepo#2
dimeloper merged 5 commits into
mainfrom
copilot/implement-contactkit-server-requirements

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 6, 2026

Implements the full @contactkit/server and @contactkit/client spec from scratch on top of the existing scaffold.

Server (@contactkit/server)

  • Env — Renamed vars to spec (EMAIL_PROVIDER, MAIL_TO, MAIL_FROM, MAIL_SUBJECT_PREFIX, ALLOWED_ORIGINS, RATE_LIMIT_MAX, RATE_LIMIT_WINDOW, LOG_LEVEL); fail-fast conditional validation (resend requires RESEND_API_KEY, smtp requires SMTP_HOST+SMTP_PORT)
  • Mailer interfacesend() now accepts replyTo and returns Promise<{ id?: string }>; both ResendMailer and SmtpMailer updated accordingly; replyTo set to submitter's email on every send
  • POST /contact — Added subject field; honeypot (website) checked pre-schema to silently return { ok: true, id } without leaking 400; Turnstile failure returns 403; response always includes a message id (provider id or crypto.randomUUID())
  • GET /health — Returns { status, uptime } (uptime in seconds since server start)
  • GET / — New endpoint returning { name, version } from package.json
  • server.ts — 16 KB body limit; trustProxy: true (Railway); rate limiting and log level wired from env
  • Dockerfile — Multi-stage build; pnpm via corepack; non-root runtime user

Client (@contactkit/client)

  • ContactClient (renamed from ContactKitClient) — send() method (was submit()); subject field; timeoutMs option (default 10 s); fetch option (was fetchFn)
  • ContactError (renamed from ContactKitError) — Exposes status: number and code: ContactErrorCode; status-to-code mapping: 400→validation, 403→captcha_failed, 429→rate_limited, 5xx→server
import { ContactClient, ContactError } from '@contactkit/client';

const client = new ContactClient({ baseUrl: 'https://contact.example.com', timeoutMs: 10_000 });

try {
  const { id } = await client.send({ name: 'Jane', email: 'jane@example.com', message: 'Hi' });
} catch (err) {
  if (err instanceof ContactError) console.error(err.code, err.status); // e.g. "rate_limited" 429
}

Tests & tooling

  • Server tests — env validation (missing MAIL_TO, missing provider creds), GET /, GET /health, valid contact (mailer called, id returned), subject+replyTo forwarding, invalid payload 400, honeypot silent 200, rate limit 429
  • Client tests — success path, error code mapping (400/429/403), NetworkError, AbortError → timeout
  • CI — Added tsc --noEmit typecheck step before lint
  • .env.example and README updated to reflect new env var names, provider setup, and self-host notes

@dimeloper dimeloper merged commit ece0877 into main May 6, 2026
1 check passed
@dimeloper dimeloper deleted the copilot/implement-contactkit-server-requirements branch May 6, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants