| # | Client | Subject | Category | Priority | Status | Assigned | Opened | SLA | |
|---|---|---|---|---|---|---|---|---|---|
| Loading tickets… | |||||||||
| Table | Key Columns | Purpose & Notes |
|---|---|---|
| profiles | id, name, email, company, website, phone, industry, timezone, plan, subscription_status, stripe_customer_id, stripe_subscription_id, subscription_current_period_end, suspended, suspended_at, pixel_id, datamoon_pixel_id, custom_limits (jsonb), support_agent, created_at, updated_at | One row per user. Mirrors auth.users. plan drives billing tier (free/spark/launch/starter/growth/scale/pro). stripe_customer_id links to Stripe; synced by stripe-webhook. suspended = true blocks portal login and bans in Supabase Auth. pixel_id = Kopimore tracking code (kp_ prefix). datamoon_pixel_id = per-client AudienceLab pixel. custom_limits = admin overrides for plan limits (max_leads, max_alerts, etc.). support_agent = assigned support team member name. |
| client_domains | id, user_id, domain, verified, added_by, created_at | Domains registered per client. Admin can add, verify, or remove. Clients read their own via RLS. Used for pixel domain validation and multi-site support. |
| client_pixels | id, user_id, pixel_domain, pixel_id, status (active/inactive/pending), created_at | Pixel configs per client domain. pixel_id auto-generated as kp_ + 16 random hex chars. status controls whether the pixel is actively collecting data. |
| admin_notes | id, user_id, note, created_by, created_at | Internal admin-only notes about clients. Never exposed to clients. No RLS policy — service role access only. Used by Client Support tab in this admin. |
| webhook_events | id, user_id, event_type, payload (jsonb), source, status, processed_at, created_at | All inbound and outbound webhook events. Full JSON payload stored for debugging and replay. status: pending, processed, failed. Added user_id in migration 20260430000001. |
| crm_settings | id, user_id, crm_type (hubspot/salesforce/other), credentials (jsonb), field_mapping (jsonb), enabled, created_at, updated_at | CRM integration config per client. credentials stores OAuth tokens (encrypted). field_mapping maps Kopimore lead fields to CRM fields. Read by push-to-crm edge function. |
| visitor_events (AudienceLab) | id, user_id, pixel_id, company_name, company_domain, industry, size, location, contact_name, contact_email, contact_title, pages_visited (jsonb), first_seen, last_seen, visit_count, utm_source, utm_medium, utm_campaign, utm_term, utm_content | Core identification data from AudienceLab. Populated by datamoon-webhook every 5 minutes. UTM columns added in migration 20260501. One row per identified visitor session per client pixel. |
| support_tickets | id, user_id, email, company, subject, description, category, status, priority, assigned_to, created_at, updated_at | Client support tickets. Created by clients via the portal support page or by admin on their behalf. status: open, in_progress, waiting_on_customer, resolved, closed. priority: urgent, high, normal, low. assigned_to = support agent name. RLS: clients see only their own tickets. Added in migration 20260503_admin_support_tables; agent/priority columns added in 20260505_support_enhancements. |
| ticket_replies | id, ticket_id, message, author, is_admin, created_at | Threaded replies on support tickets. ticket_id foreign key cascades on delete. is_admin = true for replies sent by the support team. RLS: clients can read/write replies on their own tickets. Added in migration 20260503_admin_support_tables. |
| audit_events | id, user_id, event_type, description, metadata (jsonb), created_at | Client-facing activity audit trail. Surfaced in app.kopimore.com/audit-log.html. Records login events, plan changes, domain adds, pixel events, and CRM syncs. Added in migration 20260504_audit_events. |
| referrals | id, referrer_id, referred_email, status, reward_amount, created_at, converted_at | Affiliate/referral tracking. Each client has a unique referral code tied to their pixel_id. Surfaced in app.kopimore.com/affiliate.html. Added in migration 20260504_referrals. |
| Step | Detail |
|---|---|
| 1. Signup | User submits /signup.html form. Supabase Auth creates row in auth.users. DB trigger (or portal onboarding) creates matching row in public.profiles with plan = 'free'. Auto-generates pixel_id. |
| 2. Email confirmation | Supabase sends confirmation email with magic link. User must verify before portal login is allowed. |
| 3. Portal login | Client portal at app.kopimore.com/login.html uses Supabase JS SDK. Session JWT stored in localStorage. On load, calls auth.getUser(), then checks profiles.suspended — redirects to error if true. |
| 4. Password reset (client-initiated) | app.kopimore.com/reset-password.html — client enters email, Supabase sends recovery link. User clicks link, sets new password via the recovery page. |
| 5. Password reset (admin-initiated) | Admin → Client Support → select client → Support Tools → Send Password Reset. Calls admin-actions → reset_password. Triggers Supabase to send recovery email to client. |
| 6. Admin access | admin.kopimore.com uses a shared admin password stored in sessionStorage['kp_admin_pw']. Sent as x-admin-password header on every API call. No JWT issued — admin functions deployed with --no-verify-jwt. |
| 7. Suspension | Admin sets suspended = true in profiles AND calls auth.admin.updateUserById with ban_duration: '876000h' (~100 years). Portal login check catches suspended flag. Unsuspend reverses both operations. |
| 8. Token refresh | Supabase JS SDK handles JWT refresh automatically. Token TTL is 1 hour; refresh token TTL is 7 days. If refresh token expires, user must re-login. |
| Plan | MRR | ARR | Notes |
|---|---|---|---|
| free | $0 | $0 | Default on signup. Feature-limited. Converts via upgrade prompt. No pixel data. |
| spark | $99/mo | $1,188 | Entry point paid. 500 visitors/mo, 1 seat. Basic visitor ID and leads. |
| launch | $249/mo | $2,988 | Small teams. Basic visitor ID, leads, alerts. |
| starter | $749/mo | $8,988 | Small teams. Adds analytics, CRM, saved searches. |
| growth | $1,999/mo | $23,988 | Most popular. Adds team seats, segments, webhooks, reports, audit log. |
| scale | $3,999/mo | $47,988 | High-volume. Higher visitor ID limits, advanced analytics. |
| pro | $6,499/mo | $77,988 | Enterprise-adjacent self-serve. Full feature access. |
| enterprise | $7,500+/mo | $90,000+ | Custom pricing. Dedicated CSM, white-glove onboarding, SLA. |
| Task | Command / Steps |
|---|---|
| Deploy website (auto) | Push any commit to main branch — GitHub Actions runs deploy.yml and rsync's to SiteGround automatically in ~10s. |
| Deploy website (manual) | Configure deploy.config.sh with your SSH credentials, then run ./deploy.sh locally. |
| Deploy edge function | supabase functions deploy admin-stats --no-verify-jwt --project-ref axenuhcpodpktsntdqxy supabase functions deploy admin-actions --no-verify-jwt --project-ref axenuhcpodpktsntdqxy supabase functions deploy datamoon-webhook --no-verify-jwt --project-ref axenuhcpodpktsntdqxy supabase functions deploy send-email --project-ref axenuhcpodpktsntdqxy supabase functions deploy stripe-checkout --project-ref axenuhcpodpktsntdqxy supabase functions deploy stripe-webhook --project-ref axenuhcpodpktsntdqxy |
| Run SQL migration | Supabase Dashboard → SQL Editor → New Query → paste migration SQL → Run. Check for errors in the output panel. |
| Rotate admin password | Supabase Dashboard → Edge Functions → admin-stats → Secrets → update ADMIN_PASSWORD. Repeat for admin-actions. Re-deploy both functions. Update your session. |
| View function logs | Supabase Dashboard → Edge Functions → select function → Logs tab. Filter by time range and severity. |
| Trigger manual AudienceLab poll | GitHub repo → Actions → Poll AudienceLab → Run workflow → Run. Check run logs for HTTP status and response body. |
| Local dev (edge functions) | supabase start → supabase functions serve — Deno runtime locally with local Postgres. Set secrets in .env.local. |
| Check deployment status | GitHub repo → Actions tab → look for green ✅ on "Deploy to SiteGround" workflow for latest commit. |
| Roll back a deploy | GitHub → Actions → re-run a previous successful deploy workflow, OR push a revert commit to main. |
| Task | Where / How |
|---|---|
| Change a client's plan | Client Support → select client → Billing & Plan → choose plan → Save Plan |
| Add a domain for a client | Client Support → select client → Domains → enter domain → Add Domain |
| Generate a pixel for a client | Client Support → select client → Pixel Tracking → enter domain → Add Pixel → copy embed code |
| Reset a client's password | Client Support → select client → Support Tools → Send Password Reset Email |
| Suspend a client | Client Support → select client → Danger Zone → Suspend Account (bans in DB + Supabase Auth) |
| Unsuspend a client | Client Support → select client → Danger Zone → Unsuspend Account (reverses both) |
| Add an internal note | Client Support → select client → Internal Notes → type note → Add Note |
| Update client profile | Client Support → select client → Support Tools → edit name/company/website → Save Profile |
| Find a user by email | Dashboard → search bar. Or Client Support sidebar search box. |
| See all free users at risk | Dashboard → "At-Risk Free Users" panel — users on pro plan 30+ days |
| Filter dashboard by plan | Dashboard → All Users table → click a plan filter chip |
| Export user list | Dashboard → All Users → use browser developer tools to access window.allUsers array |
| View & manage support tickets | Client Support → Ticket Inbox tab. Filter by status, priority, agent, or search by subject/email. Click a ticket to view thread, reply, change status/priority, or assign to an agent. |
| Assign a ticket to an agent | Client Support → Ticket Inbox → open ticket → Assign Agent dropdown → save. Agent must first be added in Support Settings. |
| Set custom plan limits for a client | Client Support → select client → Account tab → Custom Plan Limits → enter override values for leads/alerts/pixels/seats/CRM/retention → Save Limits. Leave blank to use plan defaults. |
| Assign a support agent to a client | Client Support → select client → Account tab → Support Agent dropdown → Save Agent. Assigns a named agent as the primary contact for that account. |
| View support analytics | Client Support → Analytics tab. Shows 6 KPI cards (open tickets, urgent, waiting, SLA breaches, closed this week, resolution rate) and 4 charts: tickets by category, by status, by plan, and top clients by volume. Agent workload bars show per-agent open ticket counts. |
| Configure SLA thresholds | Client Support → Settings tab → SLA Thresholds. Set response time targets (hours) for each priority level. Saved to localStorage, used by Analytics breach counter. |
| Add / manage support agents | Client Support → Settings tab → Support Agents. Add agents with name and email. Agents appear in ticket assignment dropdowns and the Analytics workload section. |
| Add / manage canned responses | Client Support → Settings tab → Canned Responses. Add shortcut labels and response text. Available in the ticket reply box via the Canned Response dropdown. |
| Service | Purpose | How Connected | Status |
|---|---|---|---|
| AudienceLab | B2B visitor identification — resolves anonymous site traffic to company and contact data | REST API polled every 5 min via datamoon-webhook edge function + GitHub Actions cron | Active |
| Google Analytics | Marketing site and portal traffic analytics, conversion tracking | GA4 tag G-57CJ77WZL3 injected in all page <head> via inline script | Active |
| Supabase | Database, auth, edge functions, real-time subscriptions, storage | Supabase JS SDK client-side; service role key server-side in edge functions | Active |
| GitHub Actions | CI/CD — auto-deploy on push + scheduled AudienceLab polling | 2 workflow files in .github/workflows/; secrets stored in repo Settings → Secrets | Active |
| HubSpot | Client-side CRM sync — clients push identified leads to their HubSpot | OAuth via crm_settings table + push-to-crm edge function | Per-client |
| Salesforce | Client-side CRM sync — enterprise clients push leads to Salesforce | Same as HubSpot — crm_type = 'salesforce' in crm_settings | Per-client |
| Zapier / Make / n8n | Clients automate lead workflows via outbound webhooks | Clients configure endpoints in app.kopimore.com/webhooks.html; webhook-ingest handles inbound | Per-client |
| Google Fonts | Inter typeface across all pages | CDN link in every <head> | Active |
| Stripe | Subscription billing — checkout sessions, customer portal, invoice history, webhook events | stripe-checkout + stripe-webhook edge functions. Sandbox keys active; swap to sk_live_... for production. Customer Portal: bpc_1TTOPcCuhgHJrtUShqbMnVSq. Webhook: we_1TTOcdCuhgHJrtUSoJ8O2EeI | Sandbox |
| Resend | Transactional email delivery — 9 branded templates for lifecycle emails | send-email edge function. API key in RESEND_API_KEY secret. Domain kopimore.com verified — DKIM, SPF, MX all live. | Active |
| Chart.js | Admin dashboard charts (bar, doughnut) | cdnjs CDN in admin <head> | Active |
| Layer | Mechanism | Notes |
|---|---|---|
| Client portal auth | Supabase JWT (email/password) | 1-hour token, 7-day refresh. Sessions in localStorage. suspended flag checked on every load. |
| Admin portal auth | Shared password via HTTP header | Password stored in sessionStorage (clears on tab close). Validated server-side in each edge function. |
| Database access | Row-Level Security (RLS) | All public.* tables have RLS enabled. Clients can only read/write their own rows. Edge functions use service role to bypass. |
| Edge function auth | x-admin-password header (admin); JWT Bearer (client) | Admin functions deployed --no-verify-jwt. Client functions verify Supabase JWT. |
| Webhook signatures | HMAC-SHA256 | webhook-ingest verifies signature on all inbound payloads before processing. |
| Sensitive data | Supabase Edge Function Secrets | API keys, service role key, admin password — never in source code. Injected at runtime. |
| GitHub secrets | Encrypted repository secrets | SSH credentials, Supabase keys — stored encrypted, only exposed to Actions runners. |
| CRM credentials | Encrypted jsonb in crm_settings | OAuth tokens stored server-side, never exposed to client JS. |
| Account suspension | DB flag + Supabase Auth ban | Dual-layer: profiles.suspended checked by portal; ban_duration: '876000h' blocks token issuance at Auth level. |
Every task needed before Kopimore goes live — tech, GHL / CRM, marketing, legal, and ops.
| Account | Plan | Status | Stripe Customer | Action |
|---|---|---|---|---|
| Loading… | ||||
| Account | Plan | Status | Team Seats | Stripe |
|---|---|---|---|---|
| Loading… | ||||
| Account | Plan | Status | Action |
|---|---|---|---|
| Loading… | |||
| Account | Plan | Status | Joined | Action |
|---|---|---|---|---|
| Loading… | ||||
| Client | Pixel Name | AL Pixel ID | Domain | Status | Created | Actions |
|---|---|---|---|---|---|---|
| Loading… | ||||||
| AL Pixel ID | Name | URL | Assigned To | Action |
|---|---|---|---|---|
| Loading… | ||||
| Referred By | Referral Code | Referred User | Status | Created |
|---|---|---|---|---|
| Loading… | ||||
| Time | Action | Target | Detail |
|---|---|---|---|
| No admin actions recorded yet in this session | |||
This page explains every technical piece of Kopimore in plain language — no coding knowledge required. Think of it as a map of all the moving parts, what they do, and what would break if one stopped working.
Kopimore is made up of three separate websites that all work together:
All three are static HTML files hosted on SiteGround and deployed automatically from GitHub whenever a change is made. Behind the scenes, they all talk to the same Supabase database and the same set of edge functions.
SiteGround is the web host — think of it as a storage unit on the internet. All of Kopimore's HTML pages, images, and scripts are stored there. When someone visits kopimore.com, SiteGround sends them those files instantly.
If this goes down: The entire website becomes unreachable. Nobody can view any page.
GitHub is where all the website's code is stored and version-tracked — like a Google Docs revision history for code. Whenever a change is saved to GitHub, a robot (called a "GitHub Action") automatically copies those files to SiteGround within about 2 minutes. This is why changes go live without anyone having to manually upload files.
If this breaks: Changes stop going live. The site still works — it just shows the old version.
Supabase is the database. Think of it as a giant, cloud-based spreadsheet that the entire application reads from and writes to. Every time a customer creates an account, configures an alert, connects their CRM, or a visitor gets identified — that information goes into Supabase.
If Supabase goes down: Customers can't log in, the dashboard shows no data, payments stop processing, and no visitor data is saved. It is the most critical dependency in the entire stack.
Edge functions are small programs that run in the cloud and do specific jobs when called upon. Think of each one as a specialist on staff — you call them up, they do one task, then hang up. They live inside Supabase and run on demand. Kopimore has the following edge functions:
DATAMOON_WEBHOOK_SECRET secret to be set.visitor_events table. Think of it as the receiving dock where all incoming visitor data gets logged.Stripe is the payments company. Kopimore never sees or stores a customer's credit card number — that's handled entirely by Stripe's secure checkout page. Kopimore just tells Stripe "this customer wants to subscribe to the Growth plan" and Stripe handles the rest: collecting the card, charging it monthly, managing refunds, and sending receipts.
Stripe has sandbox (test) keys for development — fake money, fake cards — and production (live) keys for real money. Kopimore is currently on sandbox keys. Before launch, the live keys must be set in Supabase's secrets manager. It's like flipping a single switch.
After a customer pays, Stripe needs to tell Kopimore "the payment worked." It does this by calling Kopimore's stripe-webhook edge function. This is what activates a customer's plan. Without the webhook, Stripe would take money but the customer's account would never upgrade.
Currently blocked: The Stripe sandbox keys must be swapped to production keys before any real customer can pay. This requires logging into the Stripe dashboard, getting the live keys, and adding them to Supabase Secrets.
Kopimore's core product promise is "identify anonymous website visitors." AudienceLab is the company that actually makes that identification possible. They maintain a database of 200+ million company profiles matched to IP address ranges. When Kopimore asks "who visited this website?", AudienceLab cross-references the visitor's IP address against their database and returns the company name, industry, employee count, and other firmographic details.
Critical dependency: Without the DATAMOON_WEBHOOK_SECRET secret set in Supabase, no visitor data appears anywhere in the customer portal. The API key must be obtained from the AudienceLab account and added before launch.
Every Kopimore customer installs a small snippet of code on their website (similar to how you'd install Google Analytics). This code is the pixel.js script. When a real visitor loads that customer's website, pixel.js silently runs in the background, records the visit (page URL, session duration, referral source, screen size, UTM parameters), and sends all of that information to Kopimore's servers. This is what creates the raw visitor log that AudienceLab then enriches with company data.
</body> tag. The snippet includes their unique Pixel ID so Kopimore knows which customer's account to credit the visitor to.Resend is the email delivery service. When Kopimore needs to send an email (welcome email after signup, payment confirmation, team invite, weekly report), it uses Resend to actually deliver those emails to the recipient's inbox. Resend ensures emails don't end up in spam, handles retries if delivery fails, and provides delivery reports.
Resend has been configured to send from @kopimore.com addresses with domain verification already completed. The 9 email templates (welcome, alert, invite, digest, payment confirmed/failed, trial expiry, support ticket, suspension) are built into the send-email edge function.
If Resend goes down: Customers still get access, payments still work, and visitor data still flows — but all email notifications stop. New signups won't get a welcome email, payment failures won't send a warning, and team invites won't arrive.
Kopimore lets customers automatically push their identified visitors into their existing CRM (Customer Relationship Management) tool — the software they use to track sales leads. Three CRMs are supported:
Note: HubSpot requires one additional setup step before launch — the OAuth redirect URL in the HubSpot developer app must be updated from a localhost URL to https://app.kopimore.com/crm.html. Until this is done, HubSpot connections will fail.
This file contains two pieces of information needed to connect to the Supabase database: the Project URL (the unique address of Kopimore's database) and the Anonymous Key (a public credential that allows read-only access to data you're allowed to see). Every page in the portal loads this file to know where the database is. It's safe to have these in the code — they're designed to be public; the actual security comes from Supabase's row-level rules.
This file powers the sidebar navigation that appears on every portal page. Rather than copying and pasting the same navigation HTML into 29 different pages, it's defined once in portal-nav.js and injected automatically. It also contains the kpGate() function — the feature gating system that checks if a customer's plan allows a specific action, and shows an upgrade prompt if not. It also handles the session check (redirecting to login if the user isn't authenticated).
This file creates the cookie consent banner that appears at the bottom of every marketing page — the "Accept / Decline" bar that GDPR and CCPA laws require for any website that tracks visitors in the EU or California. It records the visitor's choice in their browser's localStorage so it doesn't keep reappearing. It's deployed to all 395+ public marketing pages.
Similar to portal-nav.js but for the marketing site. It injects the standard site header (logo, navigation links) and footer (links, legal, social) into every marketing page automatically, so there's one place to update them instead of editing hundreds of files. If you ever need to change a footer link or add a nav item site-wide, this is where you'd do it.
A domain name like kopimore.com is like the address on an envelope. DNS (Domain Name System) is the postal service that figures out which server to actually deliver requests to. Kopimore uses several subdomains, each pointing to a different server or service:
kopimore.com
The main marketing website — hosted on SiteGround, deployed via GitHub Actions
app.kopimore.com
The customer portal — same SiteGround hosting, the portal/ folder in the repo
admin.kopimore.com
This admin dashboard — same SiteGround hosting, the admin/ folder in the repo
docs.kopimore.com
Documentation site — ✓ DNS configured May 13, 2026
status.kopimore.com
Uptime/status page — ✓ DNS configured May 13, 2026
sales.kopimore.com
Sales portal for outbound team — its own folder in the repo
Authentication is the system that checks who you are when you log in. Kopimore uses Supabase Auth, which handles this entirely. Here's exactly what happens when a customer logs in:
Customer types their email and password on the login page.
The login page sends those credentials to Supabase. Supabase checks the password (which is stored as a scrambled hash — never plain text) and either approves or rejects the login.
If approved, Supabase returns a session token — a long string of characters that acts as a temporary badge proving identity. This token is saved in the browser.
Every portal page checks for this token when it loads (via portal-nav.js). If found and valid, the page loads normally. If missing or expired, the customer is redirected to the login page.
When they log out, the token is deleted from the browser and Supabase invalidates it. Nobody else can use it.
Passwords are never stored in readable form — not even in the database. If a password is forgotten, Supabase sends a reset email and generates a new password hash. Even Kopimore employees cannot see a customer's password.
An API key is like a password that lets one service talk to another. Kopimore's edge functions need several of these to communicate with external services. They're stored in Supabase's "Secrets" vault — a secure area separate from the code, so they can't accidentally be published to GitHub. The key secrets are:
STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET
DATAMOON_WEBHOOK_SECRET
RESEND_API_KEY
HUBSPOT_CLIENT_ID / HUBSPOT_CLIENT_SECRET
Secrets are managed at: Supabase Dashboard → Project Settings → Edge Functions → Secrets. Never paste secrets into code files or send them over email or Slack — they should only ever live in the Secrets vault.
| Plan | Customers | MRR | % of Total |
|---|---|---|---|
| Loading… | |||
| Scenario | Multiple | Valuation |
|---|---|---|
| Loading live data… | ||
| Customer | Plan | Pixel Installed | First Lead | CRM Connected | Billing Active | Health |
|---|---|---|---|---|---|---|
| Loading customer data… | ||||||
| Campaign | Channel | Status | Leads |
|---|---|---|---|
| Blog SEO — Visitor Intelligence | Organic | Active | — |
| Competitor Compare Pages | Organic | Active | — |
| Product Hunt Launch | Social | Planned | — |
| Page | Type | Est. Visits/mo | Conversion |
|---|---|---|---|
| / | Homepage | — | — |
| /blog/ | Blog index (104 posts) | — | — |
| /compare/ | Competitor pages | — | — |
| /solutions/ | Industry verticals | — | — |
Est. 2025
| Customer | Type | Records | Notes | Submitted | Status |
|---|---|---|---|---|---|
| Loading… | |||||
| Contact | Amount | Status | Date |
|---|---|---|---|
| Loading… | |||
| Name | Phone | Tags / Status | Added | |
|---|---|---|---|---|
| Loading contacts… | ||||
| Contact | Product | Amount | Status | Date |
|---|---|---|---|---|
| Loading… | ||||
| Contact | Plan | Status | Amount |
|---|---|---|---|
| Loading… | |||
| Contact | Amount | Status | Date |
|---|---|---|---|
| Loading… | |||