Linje

Reply by Email API

Let customers reply. Deliver the answer to the right object.

Give an order, ticket, invoice, or application object an opaque Reply-To address. When the customer replies normally, Linje sends the email and your metadata to a signed inbox webhook.

Use cases

Keep email replies inside the product that owns the context.

Orders and invoices

Attach a customer's answer to the order or invoice that prompted it.

Support and cases

Turn the reply into a comment on the correct ticket or application case.

Documents

Receive requested files against the right application, claim, or review.

Why reply routes

Make correlation explicit instead of reconstructing it later.

Without a route With Linje Result
Parse subjects and threading headers Correlate by recipient address Stable application metadata
Expose a business ID in the address Use an opaque token No identifier leakage
Build inbound storage and delivery Receive a signed, replayable webhook Inspectable failure handling

Route

Create a project-scoped reply address.

curl -fsS \
  -H "Authorization: Bearer $LINJE_PROJECT_TOKEN" \
  -H "Idempotency-Key: 018f47d2-89ab-4cde-8123-456789abcdef" \
  -H "content-type: application/json" \
  -d '{
    "inbox-id":"orders",
    "metadata":{"order-id":"order_82921"}
  }' \
  https://api.linje.systems/v1/reply-routes
{
  "reply-route": {
    "id":"route_...",
    "address":"r+012345...@in.linje.systems",
    "metadata":{"order-id":"order_82921"},
    "active":true
  }
}

Use a fresh UUIDv4 as the idempotency key for each logical route. Concurrent and sequential exact retries return the same route and address; conflicting reuse returns 409. After retention cleanup, a retry returns 410 so a purged address cannot be revived or rebound.

Outbound

Use the generated address as Reply-To.

POST /v1/messages
{
  "from":"orders@tx.example.com",
  "to":["customer@example.com"],
  "reply-to":["r+012345...@in.linje.systems"],
  "subject":"Your order"
}

Inbound

The reply arrives with caller metadata.

POST https://app.example.com/linje/inbound
x-linje-inbound-id: ...
x-linje-delivery-id: ...
x-linje-timestamp: ...
x-linje-signature: sha256=...

{
  "schema":"linje.inbound-email.v1",
  "inbound-id":"...",
  "inbox-id":"orders",
  "route": {
    "id":"route_...",
    "metadata":{"order-id":"order_82921"}
  },
  "email": {
    "from":[{"email":"customer@example.com"}],
    "subject":"Re: Your order",
    "in-reply-to":["<linje...@tx.example.com>"],
    "references":[]
  },
  "content":{"text":"Can you change the address?"}
}

Lifecycle

Expire or disable addresses without changing metadata.

curl -fsS -X PATCH \
  -H "Authorization: Bearer $LINJE_PROJECT_TOKEN" \
  -H "content-type: application/json" \
  -d '{"active":false}' \
  https://api.linje.systems/v1/reply-routes/route_...

Disabled, expired, malformed, and unknown routes do not reach the target inbox webhook and do not expose caller metadata.

Route listings accept inbox-id, cursor, and limit. The default page size is 100 and the maximum is 500. Responses use next-cursor pagination.

The complete address is returned only on creation and exact idempotent retries, so store it when you create the route. Metadata is immutable and limited to 16 KiB.

Disabled and expired routes can be removed after the configured retention window; Linje retains only a minimal hash tombstone.

Inspect the complete OpenAPI 3.1 contract.

Boundary

Correlation is not workflow.

Linje tells your application which route received the email. Your application decides whether the message updates an order, creates a comment, or does nothing. Linje does not become a helpdesk, rules engine, or shared inbox.