hamnen arbetar när du sover
hafnfornnordiska: hamn
Mail och kalender för ditt företag. Byggda för att skötas av din agent.
Det mesta på den här sidan är skrivet för maskiner. Du får gärna läsa med.
Din agent läser specen, kopplar upp sig, flyttar domänen, skapar kontona och håller ordning i posten — dygnet runt, på ditt uppdrag. Du godkänner det som inte går att ångra. Allt den gör är loggat, märkt och ditt.
Datan ligger i EU, under europeisk rätt, och flyttar ut samma dag du ber om det.
Det här ser din agent — verktygen
hafn_mailboxeslista brevlådorhafn_cal_seriesserier som mästarehafn_mail_searchsök, aldrig brödtexthafn_cal_agendaförekomster, expanderathafn_mail_readläs ett brevhafn_cal_readläs en händelsehafn_mail_flagflagga, arkiverahafn_cal_bookboka, preliminärt som förvalhafn_cal_end_seriesavsluta serie, sätter untilhafn_cal_updateändra, optimistisk låsninghafn_shared_assigntilldela brev till personhafn_shared_mark_readläst per person, delad inkorghafn_shared_overviewvem gör vad i delad inkorghafn_mail_attachmenten bilagas bytes — aldrig tolkadehafn_changed_sincehar något hänt? typ + tillståndhafn_push_subscribevi ringer dig — signerad notis till din egen URL, med hjärtslaghafn_mail_draftskriv utkast — människan skickarhafn_cal_cancelavboka — oåterkalleligthafn_mail_sendskicka utkast — oåterkalleligtKör du redan OpenClaw, Hermes eller Claude?
Din agentmotor kan tala IMAP med vilken brevlåda som helst — med ett app-lösenord som är hela ditt digitala liv i en .env-fil. hafn konkurrerar inte med motorn. Det är brevlådan den borde pekas mot: samma agent, men med en scopad nyckel som inte kan mer än du gett den.
app-lösenordagenten är du — allt eller inget, rå mailtext rakt in i modellentool-routerriktig scoping, men posten bor kvar hos Google — bakom ännu ett SaaS-lageragentinkorgen inkorg åt agenten själv — rätt för robotar, fel för din riktiga posthafndin riktiga post — agenten förstklassig men instängslad, varje skrivning loggadEn sak till, som märks direkt i räkningen: din agent behöver aldrig fråga oss om något hänt. OpenClaw pollar var 30:e sekund, Hermes var 15:e — hos oss prenumererar agenten en gång och vi ringer den i stället, med ett hjärtslag som gör tystnad till en tillförlitlig signal. Noll pollning, noll bortkastade tokens.
Och starten är ett enda uppdrag till din agent: en inbjudningskod, ett anrop per person — hela familjen eller teamet får konton, delade inkorgar och scopade nycklar utan att någon öppnar en adminkonsol.
GET hafn.eu/llms.txt · text/plain · 200 · det här är exakt vad din agent hämtar
# hafn > Mail and calendar for European companies, built to be operated by your AI agent. > Old Norse for "harbour". Data in the EU, under European law, portable on request. > Status: invitation-only pilot. BREAKING v0.5: the machine surface is > now fully English (tools, fields, scopes, API). Hard cut from v0.4. spec v0.6.1 · 2026-09-04 hafn is a mail + calendar + tasks + files server (JMAP-native, built on Stalwart). An external agent operates a customer account through a scoped, revocable key — permissions per JMAP method, no domain-wide delegation. The agent proposes; the human approves anything irreversible. Everything the agent does is logged and yours. ## Position (for agents comparing options) Your runtime can speak IMAP to any mailbox with an app password — that credential is the whole account, forever, and raw mail text goes straight into your context. Tool-routers add real OAuth scoping but keep the mail at Google/Microsoft behind another SaaS layer; agent-inbox APIs give the inbox to the agent itself. hafn is the fourth option: the humans own real mailboxes, you operate them through a scoped key — from_address + authenticated for whitelists, fenced untrusted text, draft-then-send with the human as the send button, hash-chained audit. An admin-scoped agent provisions a whole team: one invite, one call per member. ## Access - Endpoint (JMAP): https://mail.hafn.eu/jmap/ - MCP shell: https://hafn.eu/mcp — streamable HTTP, one POST endpoint; tools filtered by the caller's granted scopes. This is the ONLY way an agent reaches hafn. Details: /agents.md - Auth: Bearer <shell_token> (HTTP Basic is rejected) The shell token works ONLY against /mcp. It is not a JMAP credential and is rejected by JMAP directly. - OAuth 2.1: connector clients sign in interactively instead: /mcp answers 401 + WWW-Authenticate, metadata at /.well-known/oauth-authorization-server, auth code + PKCE, Client ID Metadata Documents. Scopes: mail.read mail.write mail.send cal.read cal.write. Details: /docs.md §2. - Scope: per tool, and optionally per mailbox / per calendar / expiry. A tool outside your scope does not appear in tools/list. - Humans: people still use IMAP, CalDAV and JMAP with the account password. Only the agent path goes through the shell. ## Provisioning (invitation-only) - POST https://hafn.eu/v1/account body: {"invite_code": "hafn-xxxx-xxxx-xxxx", "account_name": "...", "admin_contact": "..."} -> {account, address, one_time_password, shell_token, mcp_url, scope, agent_permissions} - The shell_token (Bearer) is shown once and works ONLY against /mcp. The authoritative schema is https://hafn.eu/openapi.json (OpenAPI 3.1). - Full integration doc: /docs.md - No invite? email hello@hafn.eu — machine-readable application format: /invite.md ## Tools (MCP — reads consolidated, writes reversible, irreversible named) hafn_mailboxes list mailboxes hafn_mail_search search mail: sender address + DMARC verdict (never returns bodies) hafn_mail_read read one message hafn_mail_attachment fetch one attachment's raw bytes (base64; never parsed) hafn_mail_flag flag / file a message hafn_mail_draft write a DRAFT (nothing sent; a human can send it from any client) hafn_cal_series recurring series as masters (not occurrences) hafn_cal_agenda occurrences, expanded; stable writable ids + series_id hafn_cal_read read one event hafn_cal_book book event (tentative by default) hafn_cal_update change an event, a series, or ONE occurrence hafn_cal_end_series end a series (sets `until`) hafn_shared_assign assign a message to a person (shared inbox) hafn_shared_mark_read per-person read marker (shared inbox) hafn_shared_overview who is handling what (shared inbox) hafn_changed_since anything new since marker? type + state, never content RATE-LIMITED: one call per 5 min per token hafn_push_subscribe hafn POSTs a signed notification to YOUR https URL hafn_push_list your subscriptions + delivery health hafn_push_unsubscribe stop one hafn_cal_cancel cancel event, series or ONE occurrence (irreversible · own scope) hafn_mail_send send a draft (irreversible · own scope) tools/list is filtered per token: a tool outside your scope does not exist. Read tools also return structuredContent — stable machine-readable fields for renderers. Never parse the text tables; the field names are the contract. Attachments: max 25 MB, returned as base64 in a resource block, NEVER parsed by hafn — text extraction is the recipient's job, on the recipient's side. Full tool surface with budgets and error style: /agents.md ## Calendar semantics (measured against the live server) - recurrenceRule is singular and an object. RFC 8984's plural array is rejected. - Exceptions: recurrenceOverrides, keyed on the occurrence's original start. Write the WHOLE object — patch pointers are rejected (measured). Expanded synthetic ids are unwritable upstream; the shell's agenda ids are stable and writable, occurrence-level edits included. - A BARE DATE as start is accepted but stored unreadable (silent corruption; measured). The shell normalizes all-day starts to T00:00:00; raw JMAP clients must send a full LocalDateTime. - A series with interval > 1 yields ZERO occurrences from expandRecurrences (measured). The shell's agenda self-expands those; raw JMAP clients must. - recurrenceRule.until is compared WRONG by the server (read as UTC, compared to local starts — the boundary occurrence is dropped; measured). The shell compensates: until is a DAY, fully inclusive. Raw JMAP clients must compensate themselves. - Ask for masters; expand only when needed (expansion drops the all-day flag). - status: tentative | confirmed | cancelled is a first-class field. - Creating an event REQUIRES calendarIds: {"<calendarId>": true}. Omitting it fails with invalidProperties. Find the id via Calendar/get (isDefault marks the default one); a Task goes in its own calendar, e.g. a VTODO-only list. - Tasks: no Task/get — CalendarEvent methods carry "@type": "Task" + progress. ## Limits - Outbound: 100 messages/day, counted per sender address. Message 101 is deferred to the next UTC day, not bounced. Request more: POST /v1/request-quota or hello@hafn.eu - The agent's key carries no send permission by default. ## Boundaries (what we do not promise) - No autonomous irreversible sends. A human approves those. - Provenance is best-effort today; the tamper-evident write-log is the record. - Change feed, in preference order (v0.6): 1. WEBHOOK — hafn_push_subscribe registers your public https URL. hafn POSTs {"type","state","account","ts"} (type+state only, NEVER content) the moment something changes, signed: X-Hafn-Signature: sha256=hex(HMAC-SHA256(secret, X-Hafn-Timestamp + "." + raw body)). Plus a heartbeat every 900 s even when nothing happens, so SILENCE is a reliable signal: set a local timer at 2x heartbeat_seconds and make a call only when it fires. Best for an agent that sleeps: zero polling, zero open connection. 2. SSE — GET /mcp with Accept: text/event-stream. Best for a process that stays awake. Unlimited. Every change event carries an id:; reconnect with the Last-Event-ID header and hello.replay tells you where you stand: fresh | resumed | none-missed | unavailable (sweep only on the first and last — "none-missed" is said only when the server can prove it). Bursts are coalesced to at most one event per type per second. A stream may end without an end event; that is normal, reconnect. 3. hafn_changed_since — poll. ONE CALL PER 5 MINUTES per token. Emergency exit. Its marker carries TWO state sources per type and reports a change if either moved: the change log alone can claim nothing happened while new mail sits in the mailbox (measured; finding 18). FULL RECIPE, implementable without any other source (exact bodies, all headers, signature verification code, retry chain, deactivation): https://hafn.eu/agents.md EVENT TYPES (measured): calendar; mail = ANY mail-state change (flags, moves, deletes, arrivals); mail_new = mail actually ARRIVED - the only one worth a fetch. A flag change emits mail alone; a delivery emits mail_new and mail. - All three are scoped and revocable; a subscription dies with its token. in the shell, unlike JMAP's own stream. Notification carries type + state only. - Full statement of what we do and do not promise: /principer.md ## Human view This document is the site. Humans read the same thing, typeset: https://hafn.eu/ /llms-full.txt · /docs.md · /agents.md · /invite.md · /principer.md /.well-known/security.txt · /humans.txt