Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

Collect verified submissions

Ask visitors to verify an email through the Charming widget before receiving a submission.

Use the explicit ESM App contract for this feature. Verification creates no account and grants no extra App access. Make the App Public and open only the receiving operation with callers: 'anyone'. Keep administration operations members-only.

Server operation

import { kv } from 'charming:storage/[email protected]';
import { visitor } from 'charming:[email protected]';

export const manifest = {
  $schema: 'https://charm.ing/schema/app-manifest/next.json',
  id: 'enquiries',
  meta: { name: 'Enquiries' },
};
export const routes = [{
  op: 'submitEnquiry',
  callers: 'anyone',
  visitorEmail: true,
  inputSchema: {
    type: 'object', required: ['message'],
    properties: { message: { type: 'string' } }, additionalProperties: false,
  },
  handler: async ({ message }) => {
    if (!visitor.email) throw new Error('Verify your email before submitting.');
    await kv.put(`enquiry:${crypto.randomUUID()}`, { message, email: visitor.email });
    return { received: true };
  },
}];

From the form’s submit action, keep the message in your UI while the widget asks for verification:

if (!charming.verifyEmail.available) {
  // Offer the App's browser URL so the person can continue there.
  return;
}
const address = await charming.verifyEmail();
if (address === null) return;
await charming.api('enquiries').submitEnquiry({ message });

The handler reads the address from visitor.email, never from a submitted email field. It receives an address only after verification succeeds in this page and only on routes declaring visitorEmail: true. Confirmation expires after 10 minutes; verify again before a later submission. A second call starts a fresh flow for a signed-out visitor. Concurrent calls share the pending flow.

A signed-in person with a verified account can choose Share, including on a confirmed custom domain after domain sign-in. Their shared address is a snapshot and does not follow profile changes. Verification does not subscribe anyone to marketing. Explain why your App needs an address and how it uses stored submissions.

The widget’s Forget action deletes Charming’s verification records for this App. It cannot remove addresses the App has already copied into its own storage; provide the App’s own deletion path for those submissions. See Browser runtime API and Privacy and sharing.

Was this page helpful?