Steward
All docs

Docs Running a business

The support loop

The support loop answers a business's inbox. @steward drafts each reply from what the business knows: its charter, its page, its memory and its ledger. The hard ones go to the owners.

Open Support in the sidebar. Only a business's owners see its inbox.

Switching it on

  1. On Support, pick the business and tick Switched on.
  2. Set up Email (Resend) on Integrations and trust, so replies can go out.
  3. Make an inbound secret (Make a secret). It's shown once. Copy it then.
  4. Forward the business's support email to the inbound address (below), or link to the contact form.

While the loop is off, or the business is paused or over its model budget, tickets still arrive and wait in the inbox. Nothing is drafted. The switch is the same one Releases uses for the other loops.

Where messages come from

Bounces and mail from the business's own address are skipped. A reply threads onto the requester's ticket by In-Reply-To or References, or by the same subject while the ticket is open. A message never joins someone else's ticket.

Forwarding with a Cloudflare Email Worker

Route the support address (like help@yourdomain.com) to a Worker in Cloudflare Email Routing. Set two secrets on the Worker: STEWARD_URL (like https://studio.example.com/api/inbound/email/my-business) and STEWARD_SECRET. It needs the postal-mime package (npm i postal-mime).

// worker.js: forward each email to the studio's support inbox as JSON.
import PostalMime from "postal-mime";

export default {
  async email(message, env) {
    const raw = await new Response(message.raw).arrayBuffer();
    const mail = await PostalMime.parse(raw);
    const res = await fetch(env.STEWARD_URL, {
      method: "POST",
      headers: { "content-type": "application/json", authorization: `Bearer ${env.STEWARD_SECRET}` },
      body: JSON.stringify({
        from: mail.from?.name ? `${mail.from.name} <${mail.from.address}>` : mail.from?.address ?? message.from,
        to: message.to,
        subject: mail.subject ?? "",
        text: mail.text ?? "",
        html: mail.html ?? "",
        "message-id": mail.messageId ?? message.headers.get("message-id") ?? "",
        "in-reply-to": mail.inReplyTo ?? message.headers.get("in-reply-to") ?? "",
        references: mail.references ?? message.headers.get("references") ?? "",
      }),
    });
    // Ask the sender's server to retry later if the studio didn't take it.
    if (res.status >= 500) message.setReject("Temporary failure, try again later.");
  },
};

To test it without email: curl -X POST <url> -H "authorization: Bearer <secret>" -H "content-type: application/json" -d '{"from":"you@example.com","subject":"Hi","text":"Do you ship to Canada?"}'.

Tickets

Each ticket has a requester, a subject, the thread, a status (open, waiting on the customer, solved), a priority, a sentiment and tags. A ticket waiting on the customer for 7 days closes itself.

The requester's address is private to the owners. Cards in the channel, the ledger and the agents see a masked form like j***@e***.com. @steward never needs the address: a reply only ever goes to the ticket's own requester.

How a reply goes out

Each new message queues a job for @steward. It drafts a reply and calls support_reply with how sure it is (0 to 1). Then:

Owners can also write and send a reply on the Support page. That's their own call, so there's no card.

Feedback for the product

Messages tagged feedback, feature-request, bug or idea (or with a feedback line from @steward) go to the product loop's feedback inbox, marked as from support, with the ticket number and never the customer's address. The product loop groups them into tickets with the rest (see Loops). While the product loop is off, the tag on the ticket is all that's kept.

Numbers

On the Support page and in the morning brief: open tickets, cards waiting on you, the median first response time and resolution time, and CSAT. CSAT comes from a "rate this reply" link that @steward adds when a reply should settle the question (/p/<slug>/csat, 1 to 5).

Env

Tools

support_reply, support_update and support_tickets.