Docs / Start here

Make one safe send. Then build the system.

Use one API for transactional, lifecycle, and broadcast email. Start with synthetic recipients, inspect the evidence, and promote only when your domain and Project are ready.

01

Get a Project key

Sign in, create a Test Project, then open Settings → Developer. Create a named API key and copy the secret when it appears. Linnet stores only its hash and cannot show the secret again.

Keep the boundary clear.

Use a Project API key for your application. Use a separately scoped Agent key for Claude, Codex, or another MCP client. Never give an agent your API key.

export LINNET_API_KEY='ln_test_…'

02

Send a test email

Every request creates one Message for one recipient. Supply an idempotency key so a retry returns the same result instead of creating a duplicate.

curl https://app.linnet.run/v1/emails \
  -H "Authorization: Bearer $LINNET_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: first-send-001" \
  -d '{
    "from": "hello@test.linnet.invalid",
    "to": "you@test.linnet.invalid",
    "subject": "Hello from Linnet",
    "text": "Your first test email.",
    "stream": "transactional"
  }'

Open Activity → Emails to inspect acceptance, attempts, and delivery evidence. In a Test Project, delivery is simulated and labeled as test evidence.

03

Choose the right stream

Transactional

Account and service messages expected by the recipient. Suppress marketing without breaking receipts or security mail.

Lifecycle

Messages triggered by customer behavior or product events. Permission is checked again before every email.

Broadcast

Campaigns to a reviewed Audience Snapshot. The audience, exclusions, content, and schedule are frozen at approval.

04

Start a journey from your product

Import or link the Contact first with your stable user ID and the permission evidence you collected. Events never grant consent.

curl https://app.linnet.run/v1/events \
  -H "Authorization: Bearer $LINNET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "signup-user_123",
    "name": "user.signed_up",
    "contact_id": "user_123",
    "properties": { "plan": "trial" }
  }'
  • id makes the event replay-safe.
  • contact_id is your stable user ID, never an email address.
  • In Create → Journeys, choose the start event and an optional goal such as workspace.activated.

05

Receive signed delivery events

Open Settings → Developer, add a public HTTPS endpoint, choose Linnet or Resend-compatible formatting, and copy the one-time signing secret. Webhook work is durable and retried outside the Message worker.

Use the webhook attempt ledger and linked request receipt to troubleshoot a non-2xx response without exposing message bodies in operational logs.

06

Connect Claude or Codex

Design in your assistant. Linnet saves the exact emails, keeps their versions and receipts, and gives your team a place to review results. You do not need to use Linnet’s email composer.

Start with a verified Linnet account and an isolated Test Project. Create a named Agent connection under Home → Agents. Supply its one-time secret as LINNET_AGENT_KEY in the environment that starts your CLI. Do not paste the secret into a chat, saved command, or source file. An application’s Project API key is a different credential.

Codex

codex mcp add linnet \
  --url https://app.linnet.run/mcp \
  --bearer-token-env-var LINNET_AGENT_KEY

Claude Code

claude mcp add-json linnet '{"type":"http","url":"https://app.linnet.run/mcp","headers":{"Authorization":"Bearer ${LINNET_AGENT_KEY}"}}' --scope user

For a compatible hosted assistant, add https://app.linnet.run/mcp using OAuth. Sign in to Linnet and approve the exact Project on its consent page. Never paste a standing Agent key into a hosted connector. Availability depends on the host and account; CLI setup and hosted OAuth are separate connection paths.

Set up your brand from your assistant

When creating a CLI Agent key, select Allow brand setup and public artwork uploads if you want the assistant to do this work. It is optional; existing keys keep their current access. A hosted OAuth client must explicitly request the additional linnet.design scope alongside linnet.agent and obtain your consent. If your host cannot request that scope, use the CLI key connection for design setup.

Ask your assistant to read linnet_brand_get, optionally propose a brand from your public website with linnet_brand_from_website, and save the reviewed theme and voice using linnet_brand_save. The saved revision prevents overwriting somebody else’s changes. The website proposal does not save or call another model.

Your assistant can upload artwork it has already created. Use linnet_media_upload for JPG, PNG or GIF up to 1 MiB, with a description and image category. For files up to 5 MB, use linnet_media_upload_begin, upload the local file to the returned short-lived URL, then call linnet_media_upload_complete. Use the completed asset’s hosted URL in your email. Images become publicly accessible to recipients; upload only artwork intended for email. No extra Linnet image model is invoked.

Your first design

Read my brand, assets and business type. Design a polished welcome email in my brand, with complete plain text and HTML. Save it in Linnet as a draft Template using a stable request_id. Preview it with synthetic values, revise the heading, and report the saved Template and Version IDs. Do not publish or send.

Your assistant uses linnet_email_design_context and linnet_template_design_create/get/revise/preview. It supplies the design itself; these tools do not call a second model. Saved Templates appear under Files → Templates. Reuse the same request ID with identical original content after a timeout; a revision requires the latest Version ID.

Lifecycle and broadcast

  • Lifecycle: linnet_journey_design_create saves your assistant’s exact emails and sequence as an inactive review draft. Use a connected event supported by your Project’s business type or an explicitly saved Segment. Declare the consent Topic and timing; do not invent a trigger. A human publishes the current fingerprint.
  • Broadcast: read linnet_audience_context and existing Segments. If needed, linnet_create_segment saves a new reusable definition; it never grants consent or changes Contacts. Propose campaign.create through linnet_agent_operation_propose with exact subject, text, HTML, optional preheader and the selected segment_id. Human approval creates a draft. Audience approval and launch are separate decisions.
  • Transactional: keep a published Template Version pinned in your application, or let your assistant propose one exact transactional email for human review. Production sends use Linnet’s approved SES route; an Agent never receives your AWS credentials.
  • Results: use linnet_email_metrics, linnet_campaign_review, linnet_campaign_progress and linnet_campaign_results. The Campaign results tool returns classified engagement, unsubscribes, currency-separated revenue attribution and 14 UTC observation days without recipient details. Draft creation, generation, provider acceptance and recipient delivery are distinct states. Unknown engagement rates stay unknown.
You approve; the agent executes.

There is no Agent approval tool. A human reviews the exact proposal in Linnet; the Agent can then execute only the approved operation and report its receipt. Test Projects cannot reach public recipients. Production sending requires workspace-specific approval, a verified sender, a ready SES route and current policy checks. Public SMTP hosting is not available.

07

Promote with evidence

  1. Add a Sending Domain and publish the exact DKIM, SPF, return-path, and DMARC records Linnet shows.
  2. Confirm the Project’s data location, sending route, quota headroom, event coverage, and suppression behavior.
  3. Run a named seed-inbox test. Treat provider acceptance, delivery, inbox placement, and human engagement as different facts.
  4. Approve the exact production Campaign, journey Version, or API path only after its audience and safety checks are current.

08

When a request fails

Use the error code and HTTP status in your integration. Keep the request_id when contacting support; it lets us locate the exact attempt without asking for a key or message body.

Error reference

Find the next step for an API error

Open this page from a Linnet API response to see guidance for its code.

Do not retry blindly. Check whether the response asks you to change the request, wait, or inspect the current resource first.

For an error not yet covered here, include its code, HTTP status, and request ID in a support request. Do not send your API key, OAuth secret, or customer data.