Recipes
Public Intake Form
Public feedback form that drops submissions into Linear. (Beta)
Beta
Uses Apps with auth: "public" which are part of the Apps beta surface.
Anonymous users can submit feedback at a public URL; their submissions become Linear issues in your team's backlog. Total infrastructure: zero servers, two TypeScript files.
The submit Function (internal-write, called by the public route)
defineJob({
name: "feedback-submit",
description: "Drop a feedback submission into Linear.",
handler: async (ctx, input: {
subject: string; body: string; email?: string;
}) => {
const subject = input.subject.trim().slice(0, 200);
const body = input.body.trim().slice(0, 5_000);
if (!subject || !body) {
return { ok: false, error: "subject and body required" };
}
const ticket = await ctx.plugins.linearMcp.createIssue({
teamId: "EXT-FEEDBACK",
title: subject,
description: [
body,
"",
"---",
input.email ? \`From: \${input.email}\` : "From: anonymous",
].join("\n"),
});
return { ok: true, ticket: ticket.id };
},
});The public App
deployApp({
name: "feedback",
description: "Public feedback intake.",
jobs: { submit: "feedback-submit" },
routes: {
"GET /": {
auth: "public",
job: "feedback-submit", // referenced only for typing
render: () => formPage({
title: "Send us feedback",
fields: [
{ name: "subject", label: "Subject", required: true },
{ name: "body", label: "Details", required: true, type: "textarea" },
{ name: "email", label: "Your email (optional)", type: "email" },
],
submitTo: "POST /",
}),
},
"POST /": { auth: "public", job: "submit" },
},
});hrbr inspect -f ./feedback-submit.job.ts
hrbr inspect -f ./feedback-app.app.ts
# → https://feedback.<workspace>.tryharbor.aiThe trust pattern
The public route ONLY collects + writes to orbit.db / Linear via the
internal feedback-submit Function. The public route does not
have direct access to plugin tokens — the Function owns the upstream
credential.
The CRUCIAL rule for public routes: collect-only or read-only.
Never expose a Function with destructive plugin scope behind
auth: "public". The meta skill
harbor-apps
encodes this rule for agents.
Hardening
- Rate-limit via a
step.dothat consultsorbit.cachefor "this IP in the last minute" before writing. - Spam filter with
orbit.ai.classify({ labels: ["legit", "spam"] })before creating the Linear ticket. - Captcha — embed an external captcha widget in the
formPageHTML and verify the token in the submit handler.