Commit Graph

  • f7e798e4ad feat: document the AI provider and free/local model env vars (OPENAI_*, ANTHROPIC_API_KEY, AI_LOCAL_MODEL, SEARCH_*) in the env reference Matthew Meszaros 2026-07-15 18:52:07 +02:00
  • 0b1037843d feat: add a make AI_LOCAL=1 toggle that runs the backend and consumer against a local Ollama model (OLLAMA_HOST/OLLAMA_MODEL overridable), off by default Matthew Meszaros 2026-07-15 18:52:07 +02:00
  • a22a51ebcb feat: skip credits for automation AI nodes when running on a free/local model Matthew Meszaros 2026-07-15 18:52:07 +02:00
  • 8bc782f098 feat: skip credits (and the unpaid-draft unwind) for the inbox agent when running on a free/local model Matthew Meszaros 2026-07-15 18:52:07 +02:00
  • 96b8e9dcd6 feat: skip credits for contact research runs when running on a free/local model Matthew Meszaros 2026-07-15 18:52:07 +02:00
  • 44797191b8 feat: skip credits for reply drafts when running on a free/local model Matthew Meszaros 2026-07-15 18:52:07 +02:00
  • 18a98944ae feat: skip credits for the writing assistant when running on a free/local model, returning the unchanged balance Matthew Meszaros 2026-07-15 18:52:07 +02:00
  • d94bd70f88 feat: wire AI_LOCAL_MODEL into the backend and consumer provider config and pass the agent voice preamble into the dashboard agent service Matthew Meszaros 2026-07-15 18:51:51 +02:00
  • 59044aedf2 feat: the dashboard agent runs un-metered on a free/local model, streams a free_model signal, and injects the org voice profile into its system prompt so its writing sounds human Matthew Meszaros 2026-07-15 18:51:51 +02:00
  • b49b07d0f2 feat: persist a free_model flag on the agent session context so a reopened assistant tab keeps the free-model notice Matthew Meszaros 2026-07-15 18:51:51 +02:00
  • 42f2a9bf77 feat: add generation.BuildAgentVoiceRules - the shared humanizer bans plus the org voice grounding, scoped for the dashboard agent so it does not hijack the agent role Matthew Meszaros 2026-07-15 18:51:51 +02:00
  • 80452ce4b4 feat: add IsLocal() to the WritingGenerator interface and its hosted writing clients so the writing surfaces can detect a free/local model Matthew Meszaros 2026-07-15 18:51:51 +02:00
  • 84424fec8f feat: add a free/local model flag to the generation provider - ProviderConfig.Local, Provider.IsLocal(), and a local default model (llama3.1) for OpenAI-compatible endpoints Matthew Meszaros 2026-07-15 18:51:51 +02:00
  • 1a33b1d604 feat: add config.GetBoolOptional for parsing optional boolean env values such as AI_LOCAL_MODEL Matthew Meszaros 2026-07-15 18:51:51 +02:00
  • 9af21d2115 feat: document the new GET /ai/sessions/:id/messages assistant transcript endpoint in the API endpoints reference Matthew Meszaros 2026-07-15 17:54:16 +02:00
  • ed5e577ed0 feat: rebuild the AI assistant as a multi-tab workspace with a session history rail, expand-to-workspace mode, per-tab streaming, transcript rehydration and starter prompts Matthew Meszaros 2026-07-15 17:54:16 +02:00
  • 684152d699 feat: add the agent transcript client and hydrated turn and block types used to rehydrate a reopened assistant conversation Matthew Meszaros 2026-07-15 17:54:16 +02:00
  • a3e27c77da feat: add an AI agent workspace store slice holding per-tab conversations, the active tab, and expand state so assistant chats survive the panel closing Matthew Meszaros 2026-07-15 17:54:16 +02:00
  • 81bfb41d4e feat: add GET /ai/sessions/:id/messages returning a session transcript hydrated into the client turn and block shape plus any pending approval, so a reopened assistant conversation rehydrates Matthew Meszaros 2026-07-15 17:54:16 +02:00
  • 22edb6f445 feat: enrich the Sunrise sandbox with realistic history - a send funnel with done, processing, queued, replied and bounced leads, a live unified inbox, a CRM pipeline with deals, tasks and notes, reply templates, notifications, and daily campaign, warmup and email-count rollups Matthew Meszaros 2026-07-15 17:54:16 +02:00
  • 40740e17a3 feat: document the campaign lead statuses (queued, processing, done, replied, bounced, unsubscribed) in the campaigns guide Matthew Meszaros 2026-07-15 17:54:03 +02:00
  • fd36061901 feat: make the campaign live-activity empty state status-aware so a finished campaign reads as all caught up instead of waiting for the next send Matthew Meszaros 2026-07-15 17:54:03 +02:00
  • e7414f9f3b feat: surface the Done lead status in the campaign Leads table pill and the progress strip as a distinct indigo state Matthew Meszaros 2026-07-15 17:54:03 +02:00
  • dd7ae4d56c feat: derive a completed (Done) campaign lead status when every email step has been sent with no reply or bounce, across the per-lead search, the lead-status filter, and the campaign lead counts Matthew Meszaros 2026-07-15 17:54:03 +02:00
  • a0f92ab284 feat: sidebar live panel shows a connected-but-quiet workspace as green READY with a solid halo and an active one as pulsing LIVE, leaving gray only for a real disconnect so IDLE no longer looks offline Matthew Meszaros 2026-07-15 17:54:03 +02:00
  • a6ccd90090 feat: lock plan-gated sidebar tabs and pop an upgrade dialog on click - a feature the org's plan doesn't include now renders a locked, dimmed sidebar row (lock icon + plan badge) that opens an access dialog on click instead of routing to a teasing empty upsell page, mirroring how permission-gated tabs already lock and pop AccessLockedDialog; the dialog gains a plan variant (sparkle, 'X is on the Starter plan', Upgrade CTA to billing for owners / 'ask the owner' for everyone else) so every no-access sidebar tab - by role permission or by plan - consistently locks and explains itself Matthew Meszaros 2026-07-15 07:04:00 +02:00
  • 98353d9ac7 fix: make-sandbox org owner gets full dashboard access - the sandbox seed inserted the owner organization_members row without a permissions mask so it defaulted to 0 and every org-scoped route 403'd with 'you don't have access to this feature' across the whole dashboard; seed the owner with RolePermissions[RoleOwner] (0xFFFF) exactly like a real signup and repair an existing broken row on re-seed via ON CONFLICT DO UPDATE, plus grant OrganizationMember.HasPermission an owner bypass so an owner is never locked out of their own org regardless of the stored mask (matching the web client which already short-circuits owners) Matthew Meszaros 2026-07-15 06:59:35 +02:00
  • 4962765a79 fix: format the org_channel AI_DRAFT access_unibox clause to satisfy mix format --check-formatted (Elixir CI) Matthew Meszaros 2026-07-15 06:47:07 +02:00
  • 7f895f0f42 docs: document the inbox agent grounds drafts in the contact's CRM record and skips trivial one-line replies so users understand when a draft appears and what it is based on Matthew Meszaros 2026-07-14 07:33:47 +02:00
  • aa6a6a8525 feat: show the recipient address on the inbox-agent draft card so a reviewer sees who the suggested reply will go to before approving and sending Matthew Meszaros 2026-07-14 07:33:09 +02:00
  • 1dd1bf7098 docs: cross-link the inbox agent from the unibox reply-drafts guide so users discover the automatic-drafting option from the manual Draft reply flow, noting it still never sends on its own Matthew Meszaros 2026-07-14 07:32:46 +02:00
  • d038635425 feat: inbox agent skips drafting trivial acknowledgement replies - reuse replyclassify.WorthModeling on the inbound reply snippet so a one-line 'thanks'/'ok, got it' never triggers a paid 5-credit reply draft, threading the reply snippet through the InboxAgentReply hook payload; the org is only charged when there is enough human text to warrant a real reply Matthew Meszaros 2026-07-14 07:32:16 +02:00
  • 78ff73e45c feat: ground inbox-agent reply drafts in the counterpart contact's CRM record - the inbox agent now folds the replying contact's name, company, and known custom fields into the draft prompt (mirroring the manual unibox reply draft) via an optional ContactReader wired from repository.ContactRepository in both the backend and consumer, best-effort so a lookup miss just drops the grounding, so suggested replies reference what Warmbly already knows about the person Matthew Meszaros 2026-07-14 07:29:56 +02:00
  • 56de65380b feat: inbox agent that drafts a suggested unibox reply on inbound human replies for human approve/edit/discard - paid + per-org opt-in (organizations.inbox_agent_enabled) feature where the consumer's reply hook, on a non-automated reply, detaches a goroutine (panic-contained, never blocks ingest) that checks entitlement, dedupes via ai_thread_drafts partial unique indexes (one pending per thread, unique source_message_id), pre-checks balance, grounds a reply in the thread history + org voice + skills via generation.Provider.Complete, reserves a draft row then charges 5 credits (idempotency inbox_agent:<draft.ID>, row unwound on a fresh context if the charge fails so no unpaid draft lingers), and emits an org-scoped AI_DRAFT_READY event gated on access_unibox; the agent never sends - only a human POST to /unibox/agent-drafts/:id/approve sends through the normal reply path, claiming pending->approved before send with an approved->pending revert on send failure, alongside list + discard endpoints; plus the unibox awaiting_agent_draft badge + agent_drafts search scope, an AgentDraftCard in the thread view with inline-editable approve-and-send/discard, the workspace settings opt-in toggle, CanUseInboxAgent paid gate, migration 000065, and docs (inbox-agent guide, endpoints, realtime) Matthew Meszaros 2026-07-14 06:13:27 +02:00
  • a067bf5922 feat: AI action nodes in automations - ai_classify/ai_extract/ai_generate run one generation.Provider completion per node over the event data and merge the result back as a variable (ai_class, output_keys[], ai_text) so downstream conditions branch on it, charging 1 credit per node with a run_id+node_id idempotency key, refund on provider failure, out-of-credits fails only that node while the run continues, and a per-automation ai_credit_failures counter (migration 000064) auto-pauses a flow after 20 consecutive credit misses; classify/extract run deterministic (temperature 0), dry-run executes AI nodes for real and is charged but never touches the auto-pause lifecycle, provider + credit ledger wired into both backend and consumer integration services, plus the reply-classifier Layer 3 ported off its bespoke OpenAI client onto the platform provider via an injected ModelClassifyFunc (platform-paid, deterministic), a CompletionRequest.Temperature primitive threaded through the OpenAI and Anthropic providers, the flow builder palette/config panels/validation for the three AI steps, and a new AI-steps-in-automations guide Matthew Meszaros 2026-07-14 05:30:55 +02:00
  • 24a932c439 feat: Warmbly MCP server exposing the tool registry to any MCP client at /api/v1/mcp - a streamable-HTTP JSON-RPC endpoint (initialize/tools/list/tools/call/ping) authenticated by API key, where tools/list reflects only the static tools the key's permission mask allows and tools/call runs them gated by each tool's RequiredAPIPerm, send-class tools are never exposed or callable, per-key rate limits and usage logging apply, and the org's own connected MCP tools are not re-exposed; plus api/mcp.mdx documenting the connection url, bearer auth, the tool catalog, and Claude Code/Desktop and Cursor client configs, linked from endpoints and authentication Matthew Meszaros 2026-07-13 20:11:21 +02:00
  • 2aec6745fa feat: connect external MCP servers whose tools the AI assistant can use - ai_mcp_servers table with bearer tokens sealed by the org DEK cipher (never returned) and SSRF-validated https urls, a dependency-free streamable-HTTP JSON-RPC client (initialize/tools/list/tools/call over safehttp dial-time IP blocking with SSE+JSON handling and body caps), an mcp service that discovers tools on connect and contributes only enabled servers' tools to the dashboard agent as namespaced mcp_<server>_<tool> defs that are always write-class and never auto-allowed, a registry DynamicToolSource hook so per-org tools join the agent's tool set and resume executes them through ToolDefs, /ai/connections CRUD gated on manage_settings with an mcp_server audit entity and spine, a Connections settings page to add servers and review/enable discovered tools, and docs Matthew Meszaros 2026-07-13 20:05:24 +02:00
  • a2f50b6572 feat: org AI skills (playbooks) that every AI feature loads and follows - ai_skills table (org-scoped, unique name per org, 32KB content cap) with a skills service exposing CRUD plus an enabled-skills preamble injected into the dashboard agent, contact research, and reply-draft prompts and a load_skill read-tool that returns a playbook's full content by name, /ai/skills CRUD gated on manage_settings (JWT) or the AI_AGENT scope (API) with an ai_skill audit entity and spine entry, an AI skills settings page (list rows opening a right-side drawer with name, one-line description, enable toggle, and a markdown body), and docs with two example playbooks Matthew Meszaros 2026-07-13 19:46:25 +02:00
  • f48d51bf16 feat: AI contact research agent that gathers cited web findings per contact with sync and background-batch runs - contact_research_runs table plus a strict save_research schema (every signal and public_artifact must carry a url, confidence high/medium/low, signals<=5 hooks<=3, reject-and-reprompt once then fail), a text/template runtime prompt injecting org voice, contact record, objective, and 5-search/6-fetch budgets, a research service that drives search_web/fetch_url through the provider loop and charges 2 credits on save (billable even for nothing_found, refund-free since charged only on save, balance and abuse-cap pre-checked so a capped org never does free work), POST/GET /contacts/:id/research plus a 500-cap /contacts/research/batch draining through a bounded pool of 4 workers over FOR-UPDATE-SKIP-LOCKED claims (no new Kafka), an org-scoped AI_RESEARCH_PROGRESS realtime event gated to view_contacts, APIPermAIResearch bit 23, a ContactEdit Research tab and a ContactsTable bulk Research action, and docs; also org-scopes every credit and research idempotency key so a client-supplied Idempotency-Key can never replay across tenants Matthew Meszaros 2026-07-13 19:34:11 +02:00
  • 8d208d8c41 feat: org voice profile grounding every AI writing surface plus context-grounded unibox reply drafts - organizations gains product_description/icp_notes/voice_profile (000059) with a manage_settings workspace settings section, generation.BuildVoiceRules and BuildReplyRules fold the humanizer rules and org grounding into every prompt, WritingGenerator.GenerateWriting now takes a VoiceContext and a provider.Complete primitive powers single completions, POST /unibox/reply/draft assembles thread history plus the counterpart contact (custom fields and campaigns) plus the voice profile into a reply-tuned prompt that charges 2 credits with refund-on-failure and a client idempotency key and never sends, a Draft reply button fills the composer, and docs cover the voice profile and AI reply drafts Matthew Meszaros 2026-07-13 19:04:57 +02:00
  • 7f425c1624 feat: dashboard-wide AI assistant with streamed tool-use runs, per-action approvals, and per-iteration credits - agent_sessions/agent_messages/ai_tool_policies migration, aiagent service runs the M2 registry through the provider loop over a resumable jsonb transcript, streams text/tool-step/approval/done events over SSE, gates write tools behind approve/deny/always-allow (send always per-action) and charges 1 credit per iteration (budget 20, refund on provider failure, out-of-credits 402 insufficient_credits, cap 429 usage_cap_exceeded) with a resume-safe idempotency namespace, provider PreIteration budget hook, /ai/sessions endpoints (cursor list + two SSE runs) gated by membership with tools bound to the member's org-permission bits, APIPermAIAgent bit 22, ai_session audit entity + spine, and a right-side resizable panel (Cmd+I / sparkle button) with streamed text, collapsible tool steps, inline approval and draft-artifact deep-link cards, stop, new chat, and a credits/budget meter; tenancy enforced at the SQL layer and docs for the assistant, endpoints, permissions, and error codes Matthew Meszaros 2026-07-13 18:46:43 +02:00
  • 6ad2f13a39 feat: shared AI tool registry (internal/app/aitools) that runs 16 permission-gated tools as the invoking user over existing service-layer calls - Registry+Invocation enforce org-permission bits (JWT) or API-key bits per tool, ToolDefs binds handlers into the provider agent loop and Call powers direct MCP invocation; tools cover contacts (search/get/update/add_tag/remove_tag), CRM (create_task/create_deal), campaigns (list/stats/create_campaign_draft), unibox (list_threads/get_thread/draft_reply, gated on CanUseUnibox, draft never sends), create_automation_draft (disabled), and web search_web + fetch_url (webhook SSRF validator + safehttp dial-block + 2MB cap + html-to-text + 15min Redis cache + rune-safe truncation); every write audits via AuditService.LogAction so the spine refreshes teammates, registry built once in main and exposed on the handler for M3/M8 Matthew Meszaros 2026-07-13 18:15:58 +02:00
  • e07c33d3cd feat: provider-agnostic LLM agent-loop layer (OpenAI-first, Anthropic self-host connector) with RunAgent tool-use loop, approval/resume contract, pluggable web search, and extracted voice-rules builder - generation.Provider.RunAgent executes Go tool handlers over a resumable transcript with a read-auto/write-approve gate, openAIProvider drives it via a lean HTTP client retargetable with OPENAI_BASE_URL for any OpenAI-compatible endpoint and also serves the writing assistant, anthropicProvider mirrors it with tool_use/tool_result multi-turn plus hosted web_search, NewProvider prefers OPENAI_API_KEY, SearchClient supports Serper/SearXNG with a clean not-configured error, and BuildVoiceRules extracts the humanizer prompt for reuse; refreshed stale model ids to sonnet-4-6 and haiku-4-5 Matthew Meszaros 2026-07-13 17:52:09 +02:00
  • 5d6ed2a068 feat: AI credit economy with monthly plan grants, non-expiring purchased top-ups, and Stripe billing - two-pool credit_ledger (monthly allowance reset each cycle on invoice.paid, purchased pool via checkout mode=payment fulfilled webhook-only), consume drains monthly-then-purchased, 3 fixed top-up packs, /subscription/credits balance+checkout+paginated transactions gated on manage_billing, one-time trial grant, billing credits card with balance ring and pack buttons, credit_purchase/credit_grant audit entities and spine, wired plan.monthly_credits through pg_plan reads/writes, retry-safe webhook idempotency, and an AI credits guide Matthew Meszaros 2026-07-13 17:40:20 +02:00
  • b9a99f91a9 Merge pull request #67 from warmbly/feature/ios-realtime-steps-preview Matthew Meszaros 2026-07-13 16:15:18 +02:00
  • d6b7885e8b feat: mobile push notifications end to end - APNs provider-token client, device_tokens table with session-scoped register/delete endpoints, a push channel in notification preferences (web + iOS toggles), and Redis-backed immediate-then-digest batching (first event pushes now, bursts summarize when the 5h window closes) wired in backend and consumer, with iOS registration/badge sync and docs for the channel, endpoints, and APNS_* deploy env Matthew Meszaros 2026-07-13 16:11:15 +02:00
  • 927da9805d feat: kind-aware campaign sequence preview in the iOS app - decode the conditions/action jsonb the steps endpoint already returns and render wait/action nodes with plain-language summaries plus per-step branch routes (if opened within 3 days, goes to step 4), keeping the read-only edit-on-web handoff Matthew Meszaros 2026-07-13 16:11:00 +02:00
  • 38f32abb91 feat: close the iOS realtime gaps against the web spine - route EMAIL_STATUS/EMAIL_ERROR to mailboxes+analytics, SUBSCRIPTION/PLAN/BILLING/LIMIT to billing, DEAL and BOOKING to CRM, refresh analytics on inbox arrivals, and add a foreground socket nudge so a suspension-killed connection recovers instantly instead of ~30s late Matthew Meszaros 2026-07-13 16:10:47 +02:00
  • 7dc67c0c92 Merge pull request #66 from warmbly/admin-warmup-automation Matthew Meszaros 2026-07-12 09:07:22 +02:00
  • 9f1f511c8b feat: replace the AWS credential dialog's example-key placeholders with descriptive text - the AKIA-prefixed sample matched Trivy's aws-access-key-id secret rule and failed the security scan Matthew Meszaros 2026-07-12 09:04:02 +02:00
  • 262d25779c feat: realtime admin dashboard + diagnosability - new Elixir admin:platform channel (JWT-only, gated on users.admin_permissions) mirrors every platform event, the admin app connects through a /getaway-tokenized Phoenix socket client with a throttled react-query invalidation spine, a Live Events firehose page streams the platform with filters/pause/JSON expand, a System Status page runs parallel infra probes (postgres/redis/kafka/schema-registry/realtime/tracking via /admin/system/status), and the worker logs viewer gains follow mode, line-count selection, and copy Matthew Meszaros 2026-07-12 09:00:10 +02:00
  • a26d144b49 feat: continuous warmup-content refresh + full admin control surface - the scheduler now recycles the most-used AI threads each run (refresh_enabled/refresh_per_run) so fresh warmup messages keep generating after the target is reached, the warmup-content overview exposes the whole automation pipeline (AI configured/enabled/scheduled/stocked, per-segment stock vs target, daily budget), state legends explain risk-pool/health/job-status enums, and new settings pages cover worker profiles, releases, provisioning policy, and AWS credentials Matthew Meszaros 2026-07-12 08:59:59 +02:00
  • 940397d72c Merge pull request #65 from warmbly/feature/demo-sandbox Matthew Meszaros 2026-07-12 05:24:13 +02:00
  • 15127d1426 feat: rename the assignment test stub's GetDedicatedWorkerByUserID to GetDedicatedWorkerByOrgID to match the org-keyed WorkerRepository interface, so the call no longer falls through to the nil embedded interface and panics Matthew Meszaros 2026-07-11 20:01:14 +02:00
  • 7cfd9b26e9 feat: register the tracking-events Avro schema via a one-shot schema-registry-init infra service - the Rust tracking service encodes fetch-only against Schema Registry, so with no registered schema every open/click event failed serialization and was silently dropped in dev Matthew Meszaros 2026-07-11 19:48:05 +02:00
  • bb646709fa feat: return the message count from the sync SELECT and skip FETCH on empty mailboxes - a 1:* sequence set against zero messages is a server error, so every fresh mailbox failed its first sync pass before any mail arrived Matthew Meszaros 2026-07-11 19:47:14 +02:00
  • 1403ed4e80 feat: render-ready sandbox sequence templates - use the Go template dot-syntax the renderer actually supports ({{.FirstName}}/{{.Company}} instead of the snake_case placeholders that shipped literally), and linkify bare URLs into anchors during the plain-to-HTML conversion so click tracking has hrefs to wrap into tickets Matthew Meszaros 2026-07-11 19:40:38 +02:00
  • cd00d06e8b feat: select the mailbox (read-only, CONDSTORE) before each sync FETCH - the IMAP sync loop issued FETCH against a session with no selected mailbox, so every generic-IMAP sync pass failed and inbound mail never reached the consumer Matthew Meszaros 2026-07-11 19:20:29 +02:00
  • 2306167a5d feat: type the send/warmup-action/email-validation worker handlers so the dispatcher round-trip decodes their payloads - they took any and did their own value-type assertions, which can never match the JSON codec map bodies, so SEND_EMAIL events were dropped with "invalid body type" and nothing ever reached SMTP Matthew Meszaros 2026-07-11 19:18:09 +02:00
  • c896c0c919 feat: use path-style S3 addressing when an AWS endpoint override is set - virtual-hosted bucket.host requests do not resolve as buckets on LocalStack/MinIO, so every email body upload failed with NoSuchBucket in dev; real AWS (no override) keeps the default addressing Matthew Meszaros 2026-07-11 18:37:10 +02:00
  • b784f76e24 feat: select worker_id in the email repository GetByID - the column was missing from the query, so every caller saw a nil WorkerID: the email sender could never publish a send (every campaign task dead-lettered with "no worker assigned") and the worker reconciler re-ran assignment on every pass, endlessly reshuffling mailboxes between workers Matthew Meszaros 2026-07-11 18:03:20 +02:00
  • 41a95bbe0e feat: stabilize the sandbox against two live-run failure modes - seed and repair fixture contacts as verified (the live verifier finds no MX for .test domains and the pre-send gate then skips every campaign send) and deactivate the seeded workers the native stack never runs, whose active-but-heartbeatless rows kept winning placement and ping-ponging mailbox assignments with the dead-worker sweep Matthew Meszaros 2026-07-11 17:57:21 +02:00
  • e2ccd0d75a feat: record the new operational contracts in the agent notes and env example - CODEC_PROVIDER=json wherever workers run, CREDENTIALS_ENCRYPTION_KEY for credential sealing, the two native workers, and the make sandbox entry point Matthew Meszaros 2026-07-11 17:31:58 +02:00
  • 9a3551c5ea feat: document the sandbox at /development/sandbox/ (quick start, seeded data, architecture of the simulation, env vars, demo script, troubleshooting) and refresh /development/local-development/ with dovecot, the sandbox target, and the emulator host.docker.internal callback note Matthew Meszaros 2026-07-11 17:31:58 +02:00
  • 48ec53d781 feat: add the sandbox seeder and simulator (cmd/sandbox, internal/sandbox) - seeds the Sunrise Labs showcase org (paid plan, six live mailboxes with sealed mailpit/dovecot credentials, active tracked campaigns, pre-verified contacts, warmup pool membership) and repairs every fixture smtp_imap account and .test contact; the simulator plays the internet by polling mailpit, appending mail into dovecot inboxes for the real IMAP sync to pick up, hitting tracking pixels and click tickets, and replying as hash-stable contact personas with correct In-Reply-To threading Matthew Meszaros 2026-07-11 17:31:42 +02:00
  • f8cf7e930e feat: native dev targets for the sandbox - make sandbox / sandbox-seed, a second native worker (worker-premium, required because paid orgs place strictly onto premium-tier workers) joined into make run, CODEC_PROVIDER=json + CREDENTIALS_ENCRYPTION_KEY + MAIL_TLS_INSECURE in the dev env, dovecot in INFRA_SVCS, and SELF_HOST defaulting to host.docker.internal so the dockerized cloud-tasks emulator can reach the host-run backend Matthew Meszaros 2026-07-11 17:31:42 +02:00
  • 1634d195d3 feat: fix the DEK cache path in the cipher service - a redis cache miss (every first use of an org key) returned redis.Nil as a fatal error before the KMS fallback could run, and a cache hit was fetched then discarded; now a hit returns immediately and any cache miss or error falls through to KMS Matthew Meszaros 2026-07-11 17:31:23 +02:00
  • 9d7e3aa61f feat: make scheduled sending self-healing - dispatch Cloud Tasks callbacks by the task row type (all enqueues share one webhook URL, so campaign callbacks were run through the warmup handler), cancel pending tasks stranded past their slot so the reconcilers re-seed chains after a lost callback or emulator restart, and cast the status enum in the two UPDATE ... CASE queries that failed parameter type deduction on every callback Matthew Meszaros 2026-07-11 17:31:23 +02:00
  • 167cc370b3 feat: re-key dedicated_worker_assignments to organizations (migration 000055) - the assignment service has always keyed by org id but the column carried a users FK, so every runtime bind failed with an FK violation and dedicated-plan orgs could never get a worker; renames the column, remaps seed rows, and updates the repository, admin filter, and convert-to-dedicated endpoint to org semantics Matthew Meszaros 2026-07-11 17:31:23 +02:00
  • 6bc2073379 feat: honor CODEC_PROVIDER=json across backend and consumer (previously worker-only) and round-trip untyped event bodies into their typed payloads in both dispatchers - the worker command/result envelopes carry any-typed bodies that Avro cannot serialize, so worker messaging only works on the JSON codec; also wires the credentials encrypter into both mains Matthew Meszaros 2026-07-11 17:31:02 +02:00
  • 99c1c4afa9 feat: seal SMTP/IMAP credentials at rest with a CREDENTIALS_ENCRYPTION_KEY-backed encrypter - the email repository Encrypt field was never wired anywhere, so smtp_imap accounts could never load onto workers (plaintext rows failed decode; hex-looking rows would nil-panic); adds encrypt.FromEnv and nil-guards that fail with a captured error instead Matthew Meszaros 2026-07-11 17:30:49 +02:00
  • 50aa0a3b3d feat: contain panics in the per-mailbox sync loop - the worker is multi-tenant, so one mailbox hitting a bad server response must not take down every other account on the process Matthew Meszaros 2026-07-11 17:30:20 +02:00
  • ee35138e37 feat: fix the generic IMAP client so it can actually connect and sync - assign the client before auth (was nil and would panic), check CONDSTORE after login where servers advertise it, request LIST-STATUS fields (folders were silently empty), and fetch 1:* instead of an empty SeqSet that panics inside go-imap Matthew Meszaros 2026-07-11 17:30:20 +02:00
  • 932b0bc856 feat: add the MAIL_TLS_INSECURE dev knob (netbind) so worker mail clients can skip TLS verification and, for SMTP, tolerate a server with no STARTTLS - local mailpit/dovecot only; production behavior is unchanged when the env var is unset Matthew Meszaros 2026-07-11 17:30:20 +02:00
  • c8ed667aeb feat: add a dovecot IMAP service to the infra stack and accept any-credential SMTP auth on mailpit so local mailboxes can really send and sync (sandbox mail hosting), plus CODEC_PROVIDER/CREDENTIALS_ENCRYPTION_KEY env for backend, consumer, and workers and a host-gateway alias so the cloud-tasks emulator can call a natively run backend Matthew Meszaros 2026-07-11 17:30:03 +02:00
  • c3b99e598b Merge pull request #64 from warmbly/feature/ios-dashboard-app Matthew Meszaros 2026-07-11 10:59:59 +02:00
  • b379be9732 feat: qualify self.task in the openSocket admission check - with the type-checker crash worked around, Xcode 26.6 surfaces the real diagnostic: the locked closure's bare 'task' resolves to the local declared later in the function and is rejected as captured before declaration Matthew Meszaros 2026-07-11 10:44:57 +02:00
  • 04cf24c98d feat: hoist the socket url resolution out of the guard condition entirely - across three CI crashes the Xcode 26.6 type checker dies whenever the async urlProvider closure property is bound or awaited inside a guard condition under StrictConcurrency, so the read, the await and the unwrap are now three plain statements Matthew Meszaros 2026-07-11 10:41:26 +02:00
  • da1a6efbf8 feat: make the websocket urlProvider a non-optional closure with a nil-returning default because the Xcode 26.6 type checker also crashes on referencing an optional async closure property, not just on optional-chained calls; behavior is unchanged (nil result still schedules a reconnect) Matthew Meszaros 2026-07-11 10:37:33 +02:00
  • e5f5623dc4 feat: unblock iOS CI - unwrap the websocket urlProvider in two steps because 'await urlProvider?()' crashes the Xcode 26.6 Swift type checker (works on 27), and build only the arm64 simulator slice in CI since the generic destination was doubling the compile for no extra signal Matthew Meszaros 2026-07-11 10:34:07 +02:00
  • 2d00ff2f69 feat: rename the Icon Composer icon to AppIcon.icon so Xcode 26's asset catalog linker finds the AppIcon.appiconset fallback (the Warmbly name built on Xcode 27 but failed LinkAssetCatalog on the macos-26 runner), and stop truncating the iOS CI build output so failures are diagnosable Matthew Meszaros 2026-07-11 10:28:36 +02:00
  • 1c76787f24 feat: CI gains an iOS job - a paths-gated macos-26 build of ios/Warmbly.xcodeproj against the generic iOS Simulator destination with signing disabled, selecting the newest installed Xcode (the app needs the iOS 26 SDK and the Icon Composer .icon), wired into the ci-status rollup Matthew Meszaros 2026-07-11 10:21:46 +02:00
  • b4c100b7ff feat: bump excelize to v2.11.0 so the Trivy security scan passes - v2.10.1 carries CVE-2026-54063 (unbounded row index allocation in the worksheet parser, HIGH, used by the contact import/export paths); go.sum picks up the transitive golang.org/x refreshes Matthew Meszaros 2026-07-11 10:21:46 +02:00
  • 956edabca3 feat: kafka no longer fails its first boot after an unclean stop - the container now waits for the stale ephemeral /brokers/ids/1 registration to expire in zookeeper before starting (with a 6s session timeout) instead of crashing on NodeExists and relying on the restart policy Matthew Meszaros 2026-07-11 10:13:44 +02:00
  • 56c3a2e988 feat: app icon becomes a native Icon Composer document matching the Figma branding 1:1 - Warmbly.icon layers the exact 1024px Logo frame render (sky gradient, clouds) under the plane glyph as a separate Liquid Glass vector layer positioned identically to Figma, replacing the flat AppIcon asset via ASSETCATALOG_COMPILER_APPICON_NAME Matthew Meszaros 2026-07-11 10:13:44 +02:00
  • 76e6ad2466 feat: iOS analytics becomes an air-hero drawer browser cover - the sky hero stays (circular hamburger and close, translucent 7d/30d/90d period chips, glass stat chips) while the web-style tab strip and card grids give way to a section drawer (overview/deliverability/warmup/accounts with issue-tinted counts) over flat native lists; Home now opens analytics and mailboxes as covers instead of pushes Matthew Meszaros 2026-07-11 10:13:44 +02:00
  • de85abc47c feat: iOS mailboxes rebuilt end to end - the list is a full-screen drawer browser cover (warmup status scopes with live counts, search pill, multi-select bulk warmup/remove, circular close) since pushing it fought the system back swipe; connecting is an air onboarding flow with real Google/Outlook brand logos, a web-style authorize step with scope bullets, working in-app OAuth via ASWebAuthenticationSession against the new warmbly:// callback, an SMTP/IMAP wizard, and a start-warmup offer on success; the detail screen becomes editable (warmup ramp steppers, sending window and weekday bitmask chips matching the Go scheduler's Sunday-first layout, sender profile, sending limits, disconnect) with debounced optimistic PATCHes Matthew Meszaros 2026-07-11 10:13:10 +02:00
  • 07a6777536 feat: email OAuth callback page gains a native-app fallback - when there is no web opener to postMessage (an ASWebAuthenticationSession has no popup parent) it redirects to warmbly://email-oauth with provider/code/state/error so the iOS app can finish the connect; web popup behavior is unchanged and the docs describe both delivery paths Matthew Meszaros 2026-07-11 10:13:10 +02:00
  • c12a621d06 feat: iOS workspace creation is the air onboarding flow - two sky-chrome steps (name, optional invites) creating the org at the end so it works both as the zero-workspaces gate (Sign out is the only exit, like the web) and as a cover with a circular close wired through explicit onClose bindings; the org picker becomes a sky screen with workspace rows and a New workspace path Matthew Meszaros 2026-07-11 10:12:41 +02:00
  • 928e1924b2 feat: iOS session handling survives network blips and explains sign-outs - token refresh only drops the session on definitive rejections (network errors rethrow and keep tokens), a missing token now kicks cleanly to sign-in instead of hanging, and the auth welcome step shows an amber session-expired notice when the app signed the user out Matthew Meszaros 2026-07-11 10:12:41 +02:00
  • e6b0bd2918 feat: iOS More tab and CRM rebuilt in the flat browser language - the hub is one full-bleed list with a workspace header and cover rows, Deals/Tasks/Meetings become full drawer browsers (scope sidebars with sliding tone capsules, search pills, end markers), and billing/notifications/profile/security/team pages drop grouped cards for eyebrow-captioned hairline rows Matthew Meszaros 2026-07-11 10:12:41 +02:00
  • ac87d80604 feat: iOS campaign leads becomes a full drawer browser matching contacts - status scopes (all/processing/replied/bounced/queued/unsubscribed) with live counts in a sky-hero sidebar, advanced contact filters, multi-select bulk edit/remove with optimistic count bumps, an add-leads picker that locks rows already in the campaign, live detail re-derived from the store with presence claims, plus shared contact browser pieces (filters sheet, CSV import, bulk actions, sidebar) and keyboard-safe create-flow sheets Matthew Meszaros 2026-07-11 10:12:09 +02:00
  • cc0871d1b8 feat: contacts search gains an additive lead_status filter plus per-status lead_counts on the first page when exactly one campaign_id is set - the SQL predicates reproduce the read-time status derivation exactly (unsubscribed > bounced > replied > active > pending via LATERAL bool_or rollups), invalid lead_status or multi-campaign combinations return 400, and the web types/table pick up the new lead_counts shape Matthew Meszaros 2026-07-11 10:11:36 +02:00
  • 343fc33b1d feat: campaign schedule board supports multiple timeframes per day - the + button now drops each new window into a free gap kept an hour clear of its neighbours (so a second tap adds a distinct morning-and-afternoon window instead of merging into one), and tapping any bar opens a precise start/end editor sheet with 30-minute wheel pickers plus remove, which is how narrow windows are tuned exactly on mobile where a ~25pt bar can't be dragged pixel-accurately; bars still drag to move and merge when overlapped Matthew Meszaros 2026-07-08 07:06:22 +02:00
  • 3c8abbb713 feat: rebuild the iOS campaign schedule as an interactive board reimagined for mobile - each day is its own full-width horizontal 24h track (not a cramped 7-column grid) with draggable window bars: tap + to add, drag a bar sideways to move, drag its white left/right handle to resize, tap × to remove, long-press a day to copy it to all or clear it, snapping to 30 minutes, with presets, a timezone menu and today highlighted; writes the authoritative schedule_windows and supports multiple independent windows per day Matthew Meszaros 2026-07-07 19:00:43 +02:00
  • 80a0ece647 feat: make the iOS campaign Settings page fully editable instead of a read-only wall - sender strategy, rotation mode and ESP matching are inline menu pickers, ramp-up is an enable toggle with start/ceiling/step steppers, and lead flow has a new-leads-per-day stepper (0=off) plus a prioritize-new-leads toggle, all PATCHing optimistically; only CC/BCC lists and tracking-domain DNS stay read-only under an 'On the web' handoff Matthew Meszaros 2026-07-07 17:33:31 +02:00
  • 430a31148a fix: campaign GetByID now selects and scans organization_id so the org gate in start/stop, the sender pool, advanced settings and tracking-domain verification stops 404ing every campaign (the field was always nil, failing campaignForOrg's non-nil org check) Matthew Meszaros 2026-07-07 17:33:31 +02:00
  • 326a07d567 feat: rebuild the iOS campaign detail as a flat hub-and-spoke instead of a card-stacked overview - the sheet is now one full-bleed native surface (eyebrow captions, bare stat columns for open/reply/bounce rates plus a live 30-day chart, hairline rows) matching the inbox and campaigns list, with Manage rows pushing to Leads (searchable, count in title), a read-only Sequence with per-step HTML reader, and Senders, all carrying a web-handoff banner for editing that needs a big screen; Sending settings (daily-limit two-disc stepper clamped 1-100, stop-on-reply, open/link tracking) edit inline via optimistic PATCH, and new Schedule (day chips, daily window, timezone picker writing schedule_windows; custom per-day schedules stay read-only) and Settings (name/description, delivery toggles, read-only advanced, creator-only delete) pages Matthew Meszaros 2026-07-07 17:14:44 +02:00
  • 836f72129f feat: detail screens get a flat full-width underline tab bar replacing the chunky scrollable pills (every tab always visible, sliding accent underline, shared by campaign, mailbox, contact, and analytics details), the white sheet is now clipped so scrolling content can never bleed over its rounded corners, and the campaign create flow presents instantly by mounting the 40fps flight-scene canvas and raising the keyboard only after the cover's slide-up finishes Matthew Meszaros 2026-07-07 10:28:46 +02:00
  • 7143533bf5 feat: make folder-heavy accounts feel first-class - the create flow's folder page switches to a two-up chip grid with a search box once there are more than eight folders (with a no-matches state), and both the campaigns and unibox drawer cascades cap their per-row stagger so long folder, mailbox, or label lists never animate for seconds Matthew Meszaros 2026-07-07 10:21:23 +02:00