Support
Help
One address, and no queue to sit in.
Kontacts is a small product in a free beta. There is no ticket portal, no chat widget and no phone tree — there is an email address, and it reaches the people who build Kontacts.
The documentation site is at docs.kontacts.dev. This page stays the support page — the address that reaches a human, and the API contracts below.
Write to us
That address runs on Kontacts itself. Mail you send to it arrives on a verified domain, lands in a Kontacts inbox, and the answer goes back out from the same address — the loop this whole product is about. It is the same path your own support@ address will take.
We do not publish a response time, because we do not have one to promise. Kontacts is free, in beta, and provided with no uptime guarantee and no service level agreement. We would rather tell you that than quote you a number we invented.
What to put in the message
Four lines get you an answer far faster than a paragraph of apology:
- The domain you connected, and whether the dashboard shows it as verified.
- The repository you linked, if the problem involves GitHub issues.
- What you expected to happen and what happened instead.
- The time, roughly, and the address the mail was sent to. That is usually enough for us to find the message in the logs.
Do not send us passwords, API keys or GitHub tokens. We will never ask for one.
Security problems
Report anything security-related to privacy@kontacts.dev rather than exploiting it, and we will not pursue you for a good-faith report. That is the commitment in our terms, not a courtesy.
Requests about your personal data — a copy of it, or having it erased — go to the same address, and our privacy policy sets out what we hold and what happens when you ask.
Answers that are already written down
- Frequently asked — whether you need your own domain, whether private repos work, what stops spam becoming issues, and how to leave.
- How it works — the project inbox, the booking page, and the form. GitHub issue routing is a mode, not the product name.
- Mailbox — a project inbox you can reply from. Forwarding vs a real mailbox is the education page.
- Pricing — Free to start, Indie Hacker billed per organisation, Enterprise custom. Signing up does not enrol you in a paid plan.
- Privacy and Terms — what we store, where, and the rules we hold ourselves to.
- Contact forms — a public POST at
/api/v1/f/{public_key}. Submissions land in the project inbox. Keys live under Settings → Forms. - Send API — mint an API key with inbox permissions under Settings → API keys, then
/api/v1/sendwith a Bearer token. - Webhooks — register an HTTPS URL under Settings → Webhooks to receive signed form, booking, and mail events. Zapier, Make, and n8n use the same generic webhook.
- MCP — connect Cursor or Claude with the same API key. List and read mail, send or reply, list domains and bookings. Not IMAP.
- Email for AI agents — the project mailbox as an agent surface: REST send and list, signed webhooks, hosted MCP. No IMAP, no SDK to install.
- Documentation site — the same product, on its own host at
docs.kontacts.dev. - /llm.txt — the same facts as a markdown file, for language models adding a project.
/llms.txtis the same body.
Contact forms
Every project can mint a public form endpoint under Settings → Forms. Visitors POST JSON, urlencoded, or multipart to /api/v1/f/{public_key}. The key is meant to live in page source — it is not a secret. Success is 201 with { ok: true, id }. The live URL looks like https://kontacts.dev/api/v1/f/{public_key}; Settings → Forms prints yours. Each form also has a hosted page at /f/{public_key} (or /p/{project}/f/{form}) that works without login. Embed that page in an iframe on a third-party origin; add the origin under Allowed origins if you POST from your own HTML instead of the iframe.
Recommended fields are name, email, and message (or body), with an optional subject. Extra keys are kept and rendered in the inbox body — nothing is silently dropped. The row is a normal inbox message (reply, archive, spam path). Every submission is held: the sender typed their own address, and nothing outside the form vouches for it.
Allowed origins are configured per form. An empty list accepts every origin, including localhost. Once you add origins, add localhost yourself if you still want to test locally. CORS echoes the request origin on success and on errors so the browser can read the body. Rate limit is 10 submissions per IP per hour. Optional honeypot fields _gotcha / _hp and fill-time _ts are honoured when the client sends them. Idempotency is Idempotency-Key or body _idem. Configure endpoints under Settings → Forms.
<form action="https://kontacts.dev/api/v1/f/{public_key}" method="POST">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<input type="hidden" name="_gotcha">
<button type="submit">Send</button>
</form>fetch("https://kontacts.dev/api/v1/f/{public_key}", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email, message }),
});Send API
Workspace admins mint and revoke keys under Settings → API keys in the dashboard, choosing which projects a key reaches and an inbox permission of at least write to send with it. The plaintext is shown once, at create; we store a hash. Put the token on Authorization: Bearer … and POST JSON to /api/v1/send. The body is { to, subject, text, html? }. There is no from field — the project identity decides the address. Success is 201.
curl -X POST https://kontacts.dev/api/v1/send \
-H "Authorization: Bearer gm_live_…" \
-H "Content-Type: application/json" \
-d '{"to":"teammate@example.com","subject":"Hello","text":"Sent from the API"}'Free and Indie send caps still apply — the same organisation pool the dashboard composer spends. A reply in an existing thread goes through the inbox, not this endpoint.
Kontacts also has a documentation site, at docs.kontacts.dev.
Webhooks
Project admins register HTTPS URLs under Settings → Webhooks. We POST signed JSON to each live endpoint for mail, form submissions, and bookings. Events are email.received, email.sent, email.bounced, form.submitted, booking.created, booking.cancelled, webhook.test. The signing secret is shown once at create (or when you rotate it). Mail payloads are envelope metadata — no API keys, no signing secrets, no message body. form.submitted includes the sanitized field map so Zapier / Make / n8n can write a row without a second fetch.
Verify X-Kontacts-Signature as HMAC-SHA256 hex of ${timestamp}.${rawBody}, with the Unix timestamp from X-Kontacts-Timestamp. Answer 2xx. We retry with backoff and disable the endpoint after repeated failures; last delivery status is on the settings page. Send test from that page to fire webhook.test.
{
"id": "delivery-uuid",
"type": "email.received",
"created_at": "2026-09-03T20:00:00.000Z",
"data": {
"email_id": "email-uuid",
"provider_id": "resend-email-id",
"from": "ada@example.com",
"to": ["inbox@acme.com"],
"subject": "Hello",
"direction": "inbound"
}
}{
"id": "delivery-uuid",
"type": "form.submitted",
"created_at": "2026-09-05T12:00:00.000Z",
"data": {
"email_id": "email-uuid",
"form_endpoint_id": "form-uuid",
"form_name": "Contact form",
"from": "ada@example.com",
"fields": {
"name": "Ada Lovelace",
"email": "ada@example.com",
"message": "Hello"
}
}
}{
"id": "delivery-uuid",
"type": "booking.created",
"created_at": "2026-09-05T12:00:00.000Z",
"data": {
"booking_id": "booking-uuid",
"meeting_type_id": "meeting-type-uuid",
"meeting_title": "Intro call",
"starts_at": "2026-09-06T14:00:00.000Z",
"ends_at": "2026-09-06T14:30:00.000Z",
"status": "confirmed",
"invitee_email": "ada@example.com",
"invitee_name": "Ada Lovelace",
"invitee_timezone": "Europe/Paris",
"join_url": "https://meet.google.com/xxx"
}
}const crypto = require("crypto");
function verify(secret, timestamp, rawBody, signatureHeader) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`, "utf8")
.digest("hex");
const token = (signatureHeader || "")
.split(",")
.map((p) => p.trim())
.find((p) => p.startsWith("v1="));
const given = token ? token.slice(3) : "";
return given.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}Zapier, Make, n8n
- In Zapier: create a Zap → Webhooks by Zapier → Catch Hook. Copy the hook URL.
- In Make: add a Custom webhook module, create a hook, copy the URL.
- In n8n: add a Webhook node (POST), activate the workflow, copy the production URL.
- In Kontacts: Settings → Webhooks → paste that HTTPS URL → Add webhook. Copy the signing secret now.
- Click Send test. Your Catch Hook / Custom webhook / n8n node should receive type webhook.test with a valid HMAC.
- Filter on the X-Kontacts-Event header (or body.type). For a form → Notion/Slack flow, keep form.submitted.
- Map data.fields.* (forms) or data.invitee_email / data.starts_at (bookings) into the next step.
- Optional: a Code step recomputes HMAC-SHA256(secret, `${timestamp}.${rawBody}`) and compares it to the v1= token on X-Kontacts-Signature. The timestamp is X-Kontacts-Timestamp (Unix seconds).
Kontacts also has a documentation site, at docs.kontacts.dev.
MCP for agents
The same API key you mint under Settings → API keys authenticates a hosted MCP server at /api/v1/mcp. POST JSON-RPC with Authorization: Bearer …. There is no session-cookie path and no dashboard scraping. This is not IMAP and not a mail client — agents get the project mailbox, not Thunderbird.
Tools, reads first: list_projects, key_info, list_emails, get_email, list_domains, list_bookings, list_meeting_types, list_contacts, get_board, list_cards, list_comments, list_audiences, list_subscribers, list_issues, get_issue, get_issue_stats, preview_issue, list_forms, get_form, list_submissions, get_submission, send_email, reply_email, add_domain, verify_domain, create_meeting_type, set_meeting_type_active, set_availability, create_contact, update_contact, delete_contact, publish_kanban, set_kanban_public_editing, update_board, add_column, rename_column, move_column, delete_column, create_card, update_card, move_card, delete_card, delete_comment, create_audience, set_audience_enabled, create_draft, update_draft, send_issue, create_form, update_form, set_form_fields, set_form_enabled, set_form_origins, archive_form, duplicate_form, release_submission. send_email is the same stack as /api/v1/send. reply_email uses the inbox reply path (project identity From) and needs confirm: true before it sends. Free and Indie send caps still apply. publish_kanban and set_meeting_type_active need the same confirm: true before they put a board or a booking page on the public internet — one call without it returns the URL that would go live and changes nothing. Taking a page back down needs no confirm, and create_meeting_type alone publishes nothing: it makes a private draft.
Cursor and Claude take a remote URL. Put the key in a header, not in a prompt:
{
"mcpServers": {
"kontacts": {
"url": "https://kontacts.dev/api/v1/mcp",
"headers": {
"Authorization": "Bearer gm_live_…"
}
}
}
}claude mcp add --transport http kontacts https://kontacts.dev/api/v1/mcp \ --header "Authorization: Bearer gm_live_…"
The live URL is https://kontacts.dev/api/v1/mcp. Rate limit is 60 requests per minute per key.
Kontacts also has a documentation site, at docs.kontacts.dev.
Things we cannot fix for you yet
Some limits are the product, not a fault, and support cannot lift them:
- Sending is capped at 10 messages per organisation, per UTC day on the Free plan — one pool shared by every project in the organisation, not 10 each. Indie Hacker raises it to 200 a day and 1000 a month. Receiving is unmetered.
- Receiving needs a domain you can add DNS records to. There is no ready-made address on kontacts.dev to borrow — it is planned, not built.
- There is no self-serve export and no IMAP. Ask us and we will send you what we hold.