Compliance hooks
The headless layer behind the 10DLC and E911 forms: register brands, campaigns, and emergency addresses, and follow a registration to approval. Added in @handset/react 0.4.0.
useCompliance
const { registerBrand, registerCampaign, registerE911, isSubmitting, error, reset } = useCompliance();
const brand = await registerBrand({
legalName: "Bayview Dental LLC",
ein: "12-3456789",
entityType: "private_company",
contactEmail: "ops@bayviewdental.com",
phone: "+14155550142",
street: "500 Ocean Ave", city: "San Francisco", state: "CA", postalCode: "94112",
});
const campaign = await registerCampaign({
tenantId, brandId: brand.id,
useCase: "appointment_reminders",
description: "Appointment reminders and reschedule links.",
sampleMessages: ["Your appointment is tomorrow at 2 PM. Reply C to confirm.", "Reply R to reschedule."],
optInDescription: "Patients check a consent box on the booking form and agree to appointment texts.",
});
await registerE911({ tenantId, street: "500 Ocean Ave", city: "San Francisco", state: "CA", postalCode: "94112" });Each mutation takes camelCase input, maps it to the API body, and resolves with the created resource (or throws — error holds the last failure, isSubmitting is true while any one is in flight). The API enforces the carrier rules the forms mirror: a valid EIN and E.164 phone, 2–5 sampleMessages, and an optInDescription of at least 40 characters. This is the hook behind brand-registration-form, campaign-registration-form, and e911-address-form.
useBrand / useCampaign
const { brand } = useBrand(brandId, { pollMs: 15000 });
brand?.status; // "pending" → "approved" | "rejected"
brand?.rejection_reason; // set when rejected
const { campaign } = useCampaign(campaignId);
campaign?.status;
campaign?.throughput; // { messages_per_minute, daily_cap } once approvedBoth read a single registration and poll while it's pending, automatically stopping once it settles (approved or rejected) so you're not polling a decided brand forever. Pass pollMs: 0 to fetch once. This pair drives the compliance-status component.
Settled check
import { complianceSettled } from "@handset/react";
complianceSettled(brand?.status); // false while pending, true once approved/rejectedThe same predicate the hooks use to decide when to stop polling — handy if you drive your own UI off a registration's status.