Ai-Mee Help Centre
Home
Features
How-To Guides
FAQ
Need Help?
Home
Features
How-To Guides
FAQ
Need Help?

AI Marketing Platform - Technical Exploration

Deep-dive reference. For quick lookups (commands, env var list, DB table summary, publishing adapters), prefer docs/QUICK_REFERENCE.md — it's shorter and updated more often. This doc goes one level deeper: full per-route breakdowns, the complete bot tool/cron inventory, and DB column detail. Last verified against the codebase 2026-09-13 (see #1251); if it drifts again, prefer the source (api/src/routes/, api/src/bot/, front-end/src/) over this file.

1. API Routes Structure (api/src/routes/)

The API registers 24 route plugins in api/src/index.ts, plus a handful of blog/* routes that run in a separate Fastify instance (buildBlogApp(), see api/src/blog/app.ts) for the multi-tenant hosted blog. The breakdown below covers the higher-traffic/content files in detail; the rest are summarized in the table at the end of this section.

posts.ts

Purpose: Post generation, regeneration, scheduling, publishing, and email templates.

Key Endpoints:

  • POST /generate_post — create posts for a campaign or customer
  • POST /bot/generate_post — worker-only bot-initiated generation (text/plain body)
  • POST /post/init — create parent post record (returns post_id)
  • POST /post/:id/generate — generate content for an existing post
  • POST /post/:id/platform / PUT /post/:id/platform — save platform-specific content
  • POST /post/:platformPostId/generate-image — generate an image for one platform post
  • POST /regenerate_post_title, /edit_post — title regen and AI-powered content editing
  • POST /plan_posts, /plan_post_schedule — AI post planning
  • POST /publish_post — publish to a connected channel
  • POST /platform-post/:id/approve|reject|unapprove, /replay
  • GET /post/:id — full post details with all platform content
  • GET /post/:id/platform/:platform/publish-preview
  • PATCH /post/:id/schedule|unschedule, POST /post/:id/auto-schedule, /posts/schedule-bulk
  • Email template CRUD: /template/preprocess|save-mappings|save-html|ai-edit|generate|generate-branded|ensure-default|apply-stock, GET /template/stock

Database Tables Used: customer_campaign, customer_posts, customer_platform_post, client_content_asset, integration_log, post_feedback, customer_integration, customer_email_template, customer_customer, email_lists, email_segments, customer_post_email_segments

clients.ts

Purpose: Client/brand CRUD, context, knowledge base, email marketing (contacts/lists/segments), analytics, onboarding. The largest route file — roughly 55 endpoints, grouped here by resource rather than listed exhaustively:

  • Client data & context — GET /clients, /clients/search, /client/:id/context|knowledge-base|content-memory|posts|posts/scheduled|posting-schedule|analytics|dashboard; PUT /client/:id/knowledge-base|posting-schedule/:platform; POST/PUT/PATCH /clients create/update, /clients/:id/channels, /client/:id/scrape-schedule
  • Onboarding — POST /analyze-url, /onboarding/chat, /client/:id/first-post|from-conversation
  • Email marketing — contacts (/client/:id/contacts, /contacts/import), lists/segments CRUD (/email-lists, /email-segments), POST /client/:id/email/send-test, /email-template/restore, PUT /client/:id/email-provider/*
  • Media & crawling — POST /client/:id/media, /client/:id/crawl, /client/:id/content

Database Tables Used: messaging_users, client_knowledge_base, client_content_asset, post_analytics, channel_audience_snapshot, customer_platform_post, email_contacts, email_lists, email_list_members, email_segments, email_segment_members, email_send_log, email_tracking_events, customer_integration, customer_email_template, customer_customer, customer_site_page, post_generation_plan

telegram.ts

Purpose: Telegram bot pairing and notification preferences.

Key Endpoints:

  • POST /telegram/pair — pair Telegram chat to user account (DELETE to unpair)
  • GET /telegram/status — current pairing status
  • POST /telegram/pairing-token — generate a pairing token
  • PATCH /telegram/preferences — update notification prefs
  • POST /client/:id/brand-voice — add brand voice rule
  • POST /post/approve — approve/reject/request-edits on a post
  • GET /posts/pending-reminders — posts in review >24h (for cron)

Database Tables Used: messaging_users

integrations.ts

Purpose: OAuth flows and social/publishing credential management. PostPeer is the current unified social adapter — there is no "Outstand" integration in any route or in the publishing adapters (see docs/QUICK_REFERENCE.md's Publishing Adapters table for the current list). A couple of standalone scripts (api/scripts/outstand/get-post-details.ts, scripts/test-outstand.sh) still reference the old OUTSTAND_API_KEY/API, but nothing in the live route or adapter code calls them.

Key Endpoints:

  • POST /integrations/postpeer/auth-url, GET /client/:id/integrations/postpeer/accounts, PATCH .../accounts/:accountId, DELETE /client/:id/integrations/postpeer
  • POST /client/:id/analytics/sync-all, GET /client/:id/integrations/platforms
  • PUT/DELETE /client/:id/integrations/mailchimp
  • Blogger: GET /client/:id/integrations/blogger/auth-url (public), GET /integrations/blogger/callback (public)
  • PUT/DELETE .../blogger|devto|shopify|webflow|ghost|wordpress; GET .../ghost|wordpress
  • Reddit: GET .../reddit/auth-url, GET /integrations/reddit/callback (public), PUT/DELETE .../reddit
  • Google Search Console: GET /integrations/google-search-console/auth|callback (public), POST .../refresh, GET .../keywords, DELETE ...

Database Tables Used: customer_integration, customer_posting_schedule, customer_customer, gsc_keywords_cache

tracking.ts

Purpose: Email open/click/unsubscribe tracking. All endpoints public (skipAuth), rate-limited.

Key Endpoints: GET /track/open, GET /track/click, GET /track/unsubscribe, GET /track/unsubscribe/info, POST /track/unsubscribe

webhooks.ts

Purpose: Inbound provider webhooks (public, signature-verified).

Key Endpoints: POST /webhooks/resend (email delivery/bounce/complaint/unsubscribe events → email_send_log), POST /webhooks/stripe (billing events)

admin.ts

Purpose: Admin-only management endpoints, gated by assertAdmin.

Key Endpoints: GET /admin/customers, /admin/users, /admin/users/:userId; POST /admin/customers/:id/trigger-briefing|generate-posts, /admin/impersonate; PUT /admin/users/:userId/subscription, /admin/posts/:id/exclude-from-count

Database Tables Used: customer_customer, customer_posts, messaging_users, user_subscription, user_reminder_log

content.ts

Purpose: Scraping, image search, and publishing helpers.

Key Endpoints: POST /scrape, /search_images, /publish, /publish/test-email, /generate_image

Other route files

FilePurpose
auth.tsPOST /auth/bot-token-exchange (Telegram-HMAC based, public)
billing.tsStripe checkout/portal/cancel (/billing/checkout, /billing/portal, /billing/cancel)
campaigns.tsAI campaign proposals (/propose_campaign, /campaign_proposal)
chat.tsWeb dashboard chat with the bot agent (POST /chat/send, GET /chat/commands)
email-notifications.tsGET /email/status, POST /email/enable
forum.tsForum research (/client/:id/forum/searches, .../run, .../draft, .../suggest)
health.tsGET /health, GET /ready (both public)
members.tsBrand/company invites and membership management, incl. public GET /invites/:token
platforms.tsGET /platforms (static supported-platforms map, public)
prompt-logs.tsAdmin LLM cost/log inspection
public.tsPOST /public/register-interest (public, Turnstile-verified)
referrals.tsGET /referrals/me, POST /referrals/invite
whatsapp.tsWhatsApp webhook + pairing/status (public webhook endpoint)
admin-cron-jobs.tsGET /admin/cron-jobs, .../:jobName/runs — surfaces cron_job_run
admin-signups.tsSignup approval queue
blog-admin.tsBlog site/domain/post admin CRUD

2. Main API Entry Point (api/src/index.ts)

Route registration (in order): postRoutes, campaignRoutes, clientRoutes, memberRoutes, contentRoutes, telegramRoutes, whatsappRoutes, emailNotificationRoutes, integrationRoutes, trackingRoutes, webhookRoutes, billingRoutes, healthRoutes, adminRoutes, promptLogsRoutes, adminCronJobsRoutes, adminSignupsRoutes, forumRoutes, authRoutes, platformRoutes, chatRoutes, referralRoutes, publicRoutes, blogAdminRoutes.

Auth hook (global preHandler, skips / and any route with config.skipAuth: true):

  1. Reads Authorization: Bearer <token> or x-api-key.
  2. Token matches SUPABASE_SERVICE_ROLE_KEY → treated as a worker; gets createAdminSupabaseClient().
  3. Otherwise, tries local JWT verification (HS256 via SUPABASE_JWT_SECRET, or ES256/RS256 via JWKS) to fast-path detect role: service_role → also gets the admin client.
  4. Otherwise falls back to supabase.auth.getUser(token) → sets request.user and createUserSupabaseClient(token) (RLS-scoped), then runs the allowPendingUser signup-approval gate.

CORS: origin is echoed back only when there's no Origin header (server/webhook calls), in non-production, when it matches FRONTEND_URL, or when it matches the *.ai-marketing-55e.pages.dev / *.ai-mee.uk pattern; otherwise 403. credentials: true; x-api-key is an allowed header.

Health checks: GET / (public, no auth logic at all — welcome message); GET /health and GET /ready in health.ts (both skipAuth).

3. Bot Module (api/src/bot/)

The Aimee bot runs in-process inside the API (BOT_ENABLED=true), not as a separate service. It is a single LangChain tool-calling agent — there is no multi-agent team, no MCP server, and no per-agent SKILL.md files or bot/data/config.json. Files: agent.ts, tools.ts, cron.ts, index.ts, memory.ts, logger.ts, connection-errors.ts, types.ts, channels/{telegram,whatsapp,email,telegram-markdown}.ts, prompts/{system,briefing,weekly,weekly-tips}.ts.

bot/index.ts

On boot, startBot() registers a ChannelAdapter per configured channel — Telegram (if TELEGRAM_BOT_TOKEN set), WhatsApp (if WHATSAPP_ACCESS_TOKEN + WHATSAPP_PHONE_NUMBER_ID set), Email (if RESEND_API_KEY set) — each wrapped with an outbound spam guard (withSendGuard), then starts them and wires handleIncomingMessage as the callback. stopBot() tears them down on SIGTERM/SIGINT. handleIncomingMessage resolves the paired Supabase user via messaging_users, prompts for pairing if unpaired, checks BOT_COMMANDS, else mints an RLS-scoped session and calls invokeAgent.

Bot commands:

  • /stop_reminders (alias "stop reminders") — disables integration_reminders in the customer's notification settings
  • /add-note — appends a note to a client (customer_notes), auto-picking the client if the user only has one

Any other text is routed to the LangChain agent.

bot/agent.ts

Uses createToolCallingAgent + AgentExecutor from @langchain/classic/agents. Model comes from getModel('fast') (utils/llm-models.ts) — a ChatOpenAI client pointed at OpenRouter, defaulting to OPENROUTER_MODEL_FAST (google/gemini-3.5-flash-lite, env-overridable). Prompt is built from [system prompt, MessagesPlaceholder('chat_history'), human {input}, MessagesPlaceholder('agent_scratchpad')]; tools are bound via createTools(userId, contextClientId, db). The executor runs with maxIterations: 10 and surfaces tool failures back to the LLM as observations (buildToolRuntimeErrorObservation) instead of swallowing them. A BotDebugCallbackHandler logs every LLM/tool/chain event. One transient-LLM retry is allowed, but only before any tool has started.

bot/tools.ts — full tool list

search_customers, list_user_customers, fetch_client_context, update_company_info, create_notes, record_client_fact, list_campaigns, create_campaign, get_campaign_proposal, revise_campaign_proposal, edit_campaign_angle, add_campaign_angle, remove_campaign_angle, accept_campaign_proposal, reject_campaign_proposal, get_posting_schedule, set_posting_frequency, list_pending_posts, list_posts, list_scheduled_posts, get_post_status, create_post, save_platform_content, generate_content, approve_post, pending_reminders, schedule_post, schedule_multiple_posts, unschedule_post, update_post_status, get_post_link, client_dashboard, daily_briefing_summary, scrape_url, search_images, publish_content, add_brand_voice, get_site_links, search_docs — 39 tools total, each a DynamicStructuredTool wrapping a service-layer function directly (no MCP serialization). See api/src/bot/tools.ts for full input/output schemas.

bot/cron.ts — pg-boss scheduled jobs

Uses pg-boss, not node-cron. All schedules are in CRON_TIMEZONE (default UTC) and are upserted on boot; startup also runs a one-time teardown of an orphaned approval-reminders job from an earlier build.

JobSchedulePurpose
morning-briefingMon 8:30amWeekly morning briefing to paired users
weekly-summaryFri 3:00pmWeekly stats + AI narrative + tips
website-rescrapedaily 3:00amRe-crawl customer websites due for refresh
email-segment-syncdaily 2:00amSync tag-based email behavioural segments
social-analytics-syncdaily 4:00amSync social post analytics from PostPeer
channel-audience-syncdaily 4:15amSync per-channel follower/subscriber counts
schedule-replandaily 5:00amRebalance provisional post slots
knowledge-consolidationdaily 3:00amConsolidate each client's knowledge base
auto-generate-postsdaily 8:00amDispatch per-customer AI post generation
plan-campaign-proposals1st & 15th, 7:00amFortnightly campaign proposal planning
scheduled-publishevery 15 minPublish approved posts whose slot is due
integration-setup-remindersdaily 9:00amRemind customers with incomplete integrations
channel-health-alertsdaily 9:15amAlert on expiring OAuth tokens / dark channels
onboarding-remindersdaily 9:30amNudge users stalled at early onboarding
trial-lifecycledaily 10:00amTrial-ending lifecycle nudges
referral-nudgeTue 11:00amWeekly referral eligibility nudge
prompt-log-cleanupdaily 1:00amDelete expired prompt logs / log rows
billing-reconciledaily 6:00amSafety net for dropped Stripe webhooks
cron-health-watchdoghourlyAlert on stale/silently-stopped jobs
post-reconciliationhourly :20Recover posts stranded in new after failed generation
blog-domain-pollevery 5 minPoll Cloudflare for custom-domain SSL status

customer-post-generation is event-driven (fired once per active customer by auto-generate-posts), not separately scheduled.

bot/memory.ts

Conversation history lives in the chat_history Supabase table, keyed by (user_id, channel, external_id) — each user×channel pair is an independent thread. loadHistory fetches the most recent 20 messages (excluding one-way broadcasts like the morning briefing), truncates each to 4000 chars, and returns them as LangChain HumanMessage/AIMessage objects. saveHistory inserts one row per turn (non-fatal on failure, Sentry-captured).

Channel adapters

  • telegram.ts — Telegraf in long-polling mode. Listens for text messages, /start (pairing), and callback queries; routes each into handleIncomingMessage.
  • whatsapp.ts — a webhook adapter (no persistent connection); parses Meta's WhatsApp Cloud API payload and sends replies via the Graph API.
  • email.ts — send-only via Resend; no inbound handling, used only for proactive notifications (briefings, summaries, nudges).

4. Database Schema (Key Tables)

TableKey FieldsPurpose
messaging_usersuser_id, channel, external_id, is_active, paired_atGeneric multi-channel pairing (Telegram, WhatsApp, etc.) — channel-generic from its original creation, not Telegram-specific
chat_historyuser_id, channel, external_id, role, content, metadataBot conversation turns
customer_postscustomer_id, campaign_id, title, status, platforms, content_embeddingParent "post idea" record driving per-platform generation
customer_platform_postcustomer_post_id, platform, content, review_status, scheduled_at, approved_*Per-platform generated post instance with its full lifecycle
client_brand_voicecustomer_id, rule_type, rule_text, sourceBrand voice rules applied during generation
client_content_assetcustomer_id, asset_type, url, content, metadataReference brand assets used as generation context
post_analyticsplatform_post_id, metric_type, metric_valueEngagement metrics per published post
post_feedbackpost_id, feedback_type, feedback_text, admin_*Live — client/admin feedback feeding brand-voice learning
customer_integrationcustomer_id, integration, access_token, metadata, needs_reconnectOAuth/API connection state per customer per platform
marketing_calendarevent_name, event_date, event_type, industriesSeasonal/industry calendar events
integration_logpost_id, integration, resultAudit log of integration/publish attempts
customer_customername, user_id, industries, company_id, brand_paletteCore brand/client record
customer_campaigncustomer_id, name, start_date/end_date, target_post_count, goalCampaign grouping/goal container

telegram_user_mapping and telegram_client_mapping did exist historically (_archive/20260309000000_phase2_tables.sql, _archive/20260322111410_remote_schema.sql) but were dropped in favor of messaging_users (telegram_user_mapping's drop migration is _archive/20260424000001_drop_telegram_user_mapping.sql) — messaging_users is the only one of the three that's live today.

5. Environment Configuration (api/src/config/env.ts)

NODE_ENV, PORT, HOST, LOG_LEVEL

# Supabase
SUPABASE_URL, SUPABASE_KEY, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_JWT_SECRET, DATABASE_URL

# LLM — OpenRouter primary, legacy fallbacks
LLM_PROVIDER (default ollama), OPENROUTER_LLM_ENABLED, OPENROUTER_API_KEY,
OPENROUTER_MODEL_REASONING, OPENROUTER_MODEL_CREATIVE, OPENROUTER_MODEL_FAST,
GOOGLE_API_KEY, OLLAMA_BASE_URL, OLLAMA_MODEL

# Image generation
IMAGE_GENERATION_ENABLED, IMAGE_GENERATION_MODEL, IMAGE_GENERATION_BUCKET,
SHARED_POST_IMAGES, IMAGE_TEXT_OVERLAY_ENABLED

# OAuth / third-party content
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET,
SERPER_API_KEY, UNSPLASH_ACCESS_KEY

# Captcha
TURNSTILE_SECRET, HCAPTCHA_SECRET, HCAPTCHA_SITE_KEY

# Publishing
GHOST_API_URL, GHOST_ADMIN_API_KEY, POSTPEER_API_KEY

# Bot & channels
BOT_ENABLED, TELEGRAM_BOT_TOKEN, CRON_TIMEZONE,
WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_NUMBER_ID, WHATSAPP_PUBLIC_PHONE_NUMBER,
WHATSAPP_BUSINESS_ACCOUNT_ID, WHATSAPP_VERIFY_TOKEN, WHATSAPP_APP_SECRET

# Reconciliation sweep kill switches (default off — observe before arming)
RETIRE_STALE_POSTS, EXPIRE_UNREVIEWED_POSTS, EXPIRE_PARKED_POSTS,
EXPIRE_HELD_POSTS, OUTBOUND_SEND_GUARD_ENFORCE

# Email (Resend)
RESEND_API_KEY, RESEND_FROM_EMAIL, RESEND_FROM_NAME, RESEND_WEBHOOK_SECRET

# Billing (Stripe)
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET

# Security / infra
INTEGRATION_ENCRYPTION_KEY, CHROMIUM_PATH, SENTRY_DSN,
LOG_PROMPTS, LOG_PROMPTS_DIR, LOG_PROMPT_METRICS, LLM_POST_COST_ALERT_USD

# URLs
API_BASE_URL, FRONTEND_URL, DOCS_URL

# Hosted blog
BLOG_PORT, BLOG_ROOT_DOMAIN, BLOG_CNAME_TARGET, PLATFORM_BLOG_HOST,
PLATFORM_BLOG_SITE_SLUG, INDEXNOW_ENABLED,
CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID, CLOUDFLARE_PURGE_TOKEN

Every var is read only via this module (env/dynamicEnv), never process.env directly elsewhere. See the inline comments in api/src/config/env.ts for rollout posture and gotchas on individual vars.

6. Front-end Architecture (front-end/)

Stack: Vue 3.5 (Composition API), Vite 6.2, Vue Router 4.6 (file-based via unplugin-vue-router), Pinia 3.0, TypeScript 5.8, Vitest, Playwright. Styling is Tailwind CSS v4 (@theme tokens in src/css/app.css) with shadcn-vue components (built on reka-ui) — there is no Bulma dependency anywhere in package.json. Path alias is /@src/ → src/. See docs/QUICK_REFERENCE.md for commands and the full directory-structure tree; this section covers current counts and inventories only.

Pages: 49 .vue files under src/pages/**. Layout wrappers exempt from being real routes: app.vue, auth.vue, chat.vue, [...all].vue. Groups: app/ (bulk of the app — admin/, client/[id]/ with 11 sub-pages, campaign/, campaigns/, posts/, plus top-level dashboard pages), auth/ (3), chat/ (1), invite/ (1), and a handful of root-level pages (ai-preview.vue, pending.vue, register-interest.vue, unsubscribe.vue, index.vue).

Pinia stores (src/stores/*.ts, 17): aimee-chat, blog, cadence, campaign-posts, campaigns, clients, email-contacts, email-templates, integrations, notes, notifications, outbox, plan, platforms, posts, seo, user-session.

Composables (src/composables/*.ts, 28): attribution, darkmode, dropdown, fetch, field-context, image-error, notyf, screen-size, supabase, theme-colors, useAnalyticsData, useAnalyticsVerdict, useBrandIntegrations, useBreadcrumbs, useClientIntegrations, useCrawlWatcher, useCrisp, useEmailListManagement, useEmailRequirements, useEmailTemplateEdit, useForumResearch, useGettingStarted, usePlatformAddRemove, usePlatformPostSnapshots, usePostRegeneration, useSocialConnect, user-token, vuero-context.

All of the above are auto-imported (unplugin-auto-import), along with Vue, VueUse, and Vue Router APIs — no explicit imports needed in .vue files.

Component convention: use shadcn-vue components (<Card>, <CardHeader>, <CardContent>, <CardFooter>, etc., auto-imported from src/components/ui/) — never a raw <div class="box"> or Bulma classes. See the root CLAUDE.md Styling section for the full convention.

Quick Reference

Bot architecture (single agent, no MCP):

Telegram/WhatsApp/Email
    ↓
handleIncomingMessage() — resolve messaging_users pairing
    ↓
LangChain tool-calling agent (bot/agent.ts)
    ↓ (binds tools from bot/tools.ts)
tool calls → service layer functions directly
    ↓
adapter.sendMessage() → save turn to chat_history

Cron: pg-boss, ~21 scheduled jobs plus 1 event-driven — see §3 above for the full table. Morning briefing is Monday 8:30am; weekly summary is Friday 3:00pm; there is no separate 10am "reminders" job (an old one by that name is torn down at boot).

Main database relations:

  • customer_customer (1) ← → (M) customer_posts
  • customer_posts (1) ← → (M) customer_platform_post
  • customer_posts (1) ← → (M) integration_log
  • customer_customer (1) ← → (M) client_brand_voice
  • customer_customer (1) ← → (M) client_content_asset
  • customer_customer (1) ← → (M) messaging_users
  • customer_platform_post (1) ← → (M) post_analytics

Front-end: 49 pages, 17 Pinia stores, 28 composables. Tailwind CSS v4 + shadcn-vue (no Bulma). File-based routing via unplugin-vue-router.