Matthew Meszaros
44da2f464f
feat: add the control plane for mailbox sync fair use, so a mailbox syncs under an operator-editable policy and its progress survives worker replacement: a sync section on the instance settings document (backfill window in days, backfill cap per mailbox, daily new-mail budget per mailbox and per organization, each clamped on read and write with compiled defaults in constants.go), models.SyncPolicy and models.SyncState with a provider-shaped jsonb SyncCursor, a new email_sync_state table plus an index on tasks.message_id that the reply lookup was scanning sequentially without, an EmailSyncStateRepository whose Put also stamps email_accounts.last_synced_at which nothing had written since the baseline so every admin and dashboard Last synced surface read NULL, an OrganizationID and Sync block on the ADD_EMAIL payload resolved by the loader from instance settings and the saved state and, for IMAP, the saved unibox_mailboxes folder cursors that the loader had never populated so every worker restart re-walked every folder from scratch, a SYNC_STATE consumer handler that persists the relay and publishes ACCOUNT_SYNC_STATE plus a warning when the import completes or fair use flips, an internal own-conversation endpoint the worker's priority lane asks whether a new message replies to a campaign task, a mapped message or a stored thread, GET /emails/:id/sync for the dashboard, and SYNC_FLOOD and SYNC_FAIR_USE mail error codes with user copy for the two patterns that deactivate a mailbox
2026-08-18 08:43:20 -07:00
..
2026-06-01 04:23:00 +02:00
2026-05-23 16:54:12 +00:00
feat: add the control plane for mailbox sync fair use, so a mailbox syncs under an operator-editable policy and its progress survives worker replacement: a sync section on the instance settings document (backfill window in days, backfill cap per mailbox, daily new-mail budget per mailbox and per organization, each clamped on read and write with compiled defaults in constants.go), models.SyncPolicy and models.SyncState with a provider-shaped jsonb SyncCursor, a new email_sync_state table plus an index on tasks.message_id that the reply lookup was scanning sequentially without, an EmailSyncStateRepository whose Put also stamps email_accounts.last_synced_at which nothing had written since the baseline so every admin and dashboard Last synced surface read NULL, an OrganizationID and Sync block on the ADD_EMAIL payload resolved by the loader from instance settings and the saved state and, for IMAP, the saved unibox_mailboxes folder cursors that the loader had never populated so every worker restart re-walked every folder from scratch, a SYNC_STATE consumer handler that persists the relay and publishes ACCOUNT_SYNC_STATE plus a warning when the import completes or fair use flips, an internal own-conversation endpoint the worker's priority lane asks whether a new message replies to a campaign task, a mapped message or a stored thread, GET /emails/:id/sync for the dashboard, and SYNC_FLOOD and SYNC_FAIR_USE mail error codes with user copy for the two patterns that deactivate a mailbox
2026-08-18 08:43:20 -07:00
feat: make self-hosted auth work without a mail relay by rewriting the platform SMTP transport with real AUTH and TLS (it did neither, so SMTP_USERNAME/SMTP_PASSWORD were dead and every documented relay was unreachable), adding MAIL_TRANSPORT=smtp|log|ses with a log transport that prints codes so a fresh install can sign in with no relay, demoting the emailed login code to AUTH_LOGIN_CODE=always|new_device|off (off on self-host, per NIST SP 800-63B and OWASP ASVS), claiming the first owner through a single-use setup link or WARMBLY_BOOTSTRAP_* instead of register-then-psql, deriving every emailed URL from APP_URL rather than a hardcoded app.warmbly.com that leaked live reset tokens to the vendor, fixing the confirm hooks that read path params against paramless routes and broke login, register and reset confirmation in the dashboard everywhere, adding generic OIDC with PKCE, one-time state, verified nonce and (issuer,subject) identity binding, enforcing 2FA on the social paths that skipped it, adding a per-IP limiter and trusted-proxy handling to the unthrottled auth group, refusing boot on the published default secrets, and dropping mailpit from the default stack (#99)
2026-08-14 14:57:09 +02:00
2026-05-28 12:38:08 +02:00
2026-05-28 08:54:07 +02:00
2026-06-02 15:54:29 +02:00
2026-06-02 16:38:49 +02:00
2026-05-18 14:55:44 +00:00
2026-05-27 14:43:36 +00:00
2026-07-12 09:00:10 +02:00
2026-07-22 12:11:19 +02:00
2026-07-22 12:11:19 +02:00
2026-07-11 17:31:23 +02:00
2026-06-02 15:54:29 +02:00
2026-06-01 04:23:00 +02:00
2026-07-30 17:15:09 +02:00
2026-07-19 11:05:18 +02:00
2026-07-13 20:05:24 +02:00
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)
2026-07-14 06:13:27 +02:00
2026-07-18 16:30:05 +02:00
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
2026-07-13 19:34:11 +02:00
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
2026-07-13 19:46:25 +02:00
2026-07-13 19:04:57 +02:00
2026-07-21 17:34:59 +02:00
2026-06-13 07:18:23 +02:00
2026-06-12 09:38:11 +02:00
2026-06-13 07:18:23 +02:00
feat: rebuild the new-campaign wizard with animated step transitions, a numbered stepper, the shared Toggle instead of a broken hand-rolled switch, per-step validation that explains itself and a discard guard, register PopoverMenu's click-outside in the capture phase so dropdowns inside dialogs close on click-away, add a Campaigns back link and clickable breadcrumb crumbs, add a From contacts leads picker with category filter and select-all-matching backed by the bulk add_campaigns path whose SQL now scopes campaigns by organization instead of the caller, and stop self-hosted no-billing deployments presenting as a free trial or plan-metered by exposing billing_enabled on GET /auth/config, showing a Self-hosted badge, hiding Billing and Refer & earn, and reporting AI credits as unlimited with the header gauge and cost copy hidden
2026-08-18 07:48:58 -07:00
2026-07-07 05:56:35 +02:00
feat: make self-hosted auth work without a mail relay by rewriting the platform SMTP transport with real AUTH and TLS (it did neither, so SMTP_USERNAME/SMTP_PASSWORD were dead and every documented relay was unreachable), adding MAIL_TRANSPORT=smtp|log|ses with a log transport that prints codes so a fresh install can sign in with no relay, demoting the emailed login code to AUTH_LOGIN_CODE=always|new_device|off (off on self-host, per NIST SP 800-63B and OWASP ASVS), claiming the first owner through a single-use setup link or WARMBLY_BOOTSTRAP_* instead of register-then-psql, deriving every emailed URL from APP_URL rather than a hardcoded app.warmbly.com that leaked live reset tokens to the vendor, fixing the confirm hooks that read path params against paramless routes and broke login, register and reset confirmation in the dashboard everywhere, adding generic OIDC with PKCE, one-time state, verified nonce and (issuer,subject) identity binding, enforcing 2FA on the social paths that skipped it, adding a per-IP limiter and trusted-proxy handling to the unthrottled auth group, refusing boot on the published default secrets, and dropping mailpit from the default stack (#99)
2026-08-14 14:57:09 +02:00
feat: make self-hosted auth work without a mail relay by rewriting the platform SMTP transport with real AUTH and TLS (it did neither, so SMTP_USERNAME/SMTP_PASSWORD were dead and every documented relay was unreachable), adding MAIL_TRANSPORT=smtp|log|ses with a log transport that prints codes so a fresh install can sign in with no relay, demoting the emailed login code to AUTH_LOGIN_CODE=always|new_device|off (off on self-host, per NIST SP 800-63B and OWASP ASVS), claiming the first owner through a single-use setup link or WARMBLY_BOOTSTRAP_* instead of register-then-psql, deriving every emailed URL from APP_URL rather than a hardcoded app.warmbly.com that leaked live reset tokens to the vendor, fixing the confirm hooks that read path params against paramless routes and broke login, register and reset confirmation in the dashboard everywhere, adding generic OIDC with PKCE, one-time state, verified nonce and (issuer,subject) identity binding, enforcing 2FA on the social paths that skipped it, adding a per-IP limiter and trusted-proxy handling to the unthrottled auth group, refusing boot on the published default secrets, and dropping mailpit from the default stack (#99)
2026-08-14 14:57:09 +02:00
feat: make self-hosted auth work without a mail relay by rewriting the platform SMTP transport with real AUTH and TLS (it did neither, so SMTP_USERNAME/SMTP_PASSWORD were dead and every documented relay was unreachable), adding MAIL_TRANSPORT=smtp|log|ses with a log transport that prints codes so a fresh install can sign in with no relay, demoting the emailed login code to AUTH_LOGIN_CODE=always|new_device|off (off on self-host, per NIST SP 800-63B and OWASP ASVS), claiming the first owner through a single-use setup link or WARMBLY_BOOTSTRAP_* instead of register-then-psql, deriving every emailed URL from APP_URL rather than a hardcoded app.warmbly.com that leaked live reset tokens to the vendor, fixing the confirm hooks that read path params against paramless routes and broke login, register and reset confirmation in the dashboard everywhere, adding generic OIDC with PKCE, one-time state, verified nonce and (issuer,subject) identity binding, enforcing 2FA on the social paths that skipped it, adding a per-IP limiter and trusted-proxy handling to the unthrottled auth group, refusing boot on the published default secrets, and dropping mailpit from the default stack (#99)
2026-08-14 14:57:09 +02:00
2026-07-20 09:56:02 +02:00
2026-07-07 09:40:27 +02:00
2026-07-19 08:45:47 +02:00
2026-07-19 08:52:56 +02:00
2026-06-07 07:54:02 +02:00
2026-06-15 08:11:53 +02:00
2026-07-22 17:04:51 +02:00
feat: rebuild the new-campaign wizard with animated step transitions, a numbered stepper, the shared Toggle instead of a broken hand-rolled switch, per-step validation that explains itself and a discard guard, register PopoverMenu's click-outside in the capture phase so dropdowns inside dialogs close on click-away, add a Campaigns back link and clickable breadcrumb crumbs, add a From contacts leads picker with category filter and select-all-matching backed by the bulk add_campaigns path whose SQL now scopes campaigns by organization instead of the caller, and stop self-hosted no-billing deployments presenting as a free trial or plan-metered by exposing billing_enabled on GET /auth/config, showing a Self-hosted badge, hiding Billing and Refer & earn, and reporting AI credits as unlimited with the header gauge and cost copy hidden
2026-08-18 07:48:58 -07:00
2026-06-15 08:11:53 +02:00
2026-06-01 04:23:00 +02:00
2026-07-13 16:11:15 +02:00
2026-06-28 05:58:25 +00:00
2026-06-03 06:26:23 +02:00
feat: give each mailbox a human sending persona (randomized daily and hourly caps, send spacing, work start/end, lunch break and working weekdays, rolled once per local day in the mailbox's own timezone and applied across the campaign, warmup and smart-send schedulers), add campaign auto-pause guardrails that stop a campaign when its bounce, complaint or reply rate leaves the configured band, make mailbox rotation actually rotate for tag-resolved and all-mailbox campaigns, stop every scheduler from ever returning a slot in the past, and correct the mailbox min-gap field that stored seconds while labelling them minutes
2026-08-13 16:51:29 +02:00
2026-08-16 07:45:57 +02:00
2026-06-01 04:23:00 +02:00
2026-01-29 05:59:04 +01:00
feat: add the control plane for mailbox sync fair use, so a mailbox syncs under an operator-editable policy and its progress survives worker replacement: a sync section on the instance settings document (backfill window in days, backfill cap per mailbox, daily new-mail budget per mailbox and per organization, each clamped on read and write with compiled defaults in constants.go), models.SyncPolicy and models.SyncState with a provider-shaped jsonb SyncCursor, a new email_sync_state table plus an index on tasks.message_id that the reply lookup was scanning sequentially without, an EmailSyncStateRepository whose Put also stamps email_accounts.last_synced_at which nothing had written since the baseline so every admin and dashboard Last synced surface read NULL, an OrganizationID and Sync block on the ADD_EMAIL payload resolved by the loader from instance settings and the saved state and, for IMAP, the saved unibox_mailboxes folder cursors that the loader had never populated so every worker restart re-walked every folder from scratch, a SYNC_STATE consumer handler that persists the relay and publishes ACCOUNT_SYNC_STATE plus a warning when the import completes or fair use flips, an internal own-conversation endpoint the worker's priority lane asks whether a new message replies to a campaign task, a mapped message or a stored thread, GET /emails/:id/sync for the dashboard, and SYNC_FLOOD and SYNC_FAIR_USE mail error codes with user copy for the two patterns that deactivate a mailbox
2026-08-18 08:43:20 -07:00
2026-06-03 16:47:53 +02:00
2026-07-19 16:13:43 +02:00
2026-07-22 17:05:08 +02:00
2026-07-18 16:30:05 +02:00
2026-07-18 16:30:05 +02:00
feat: add the control plane for mailbox sync fair use, so a mailbox syncs under an operator-editable policy and its progress survives worker replacement: a sync section on the instance settings document (backfill window in days, backfill cap per mailbox, daily new-mail budget per mailbox and per organization, each clamped on read and write with compiled defaults in constants.go), models.SyncPolicy and models.SyncState with a provider-shaped jsonb SyncCursor, a new email_sync_state table plus an index on tasks.message_id that the reply lookup was scanning sequentially without, an EmailSyncStateRepository whose Put also stamps email_accounts.last_synced_at which nothing had written since the baseline so every admin and dashboard Last synced surface read NULL, an OrganizationID and Sync block on the ADD_EMAIL payload resolved by the loader from instance settings and the saved state and, for IMAP, the saved unibox_mailboxes folder cursors that the loader had never populated so every worker restart re-walked every folder from scratch, a SYNC_STATE consumer handler that persists the relay and publishes ACCOUNT_SYNC_STATE plus a warning when the import completes or fair use flips, an internal own-conversation endpoint the worker's priority lane asks whether a new message replies to a campaign task, a mapped message or a stored thread, GET /emails/:id/sync for the dashboard, and SYNC_FLOOD and SYNC_FAIR_USE mail error codes with user copy for the two patterns that deactivate a mailbox
2026-08-18 08:43:20 -07:00
2026-06-28 19:09:37 +02:00
2026-06-30 10:06:19 +02:00
2026-06-10 17:16:07 +02:00
2026-06-10 17:16:07 +02:00
2026-06-02 15:54:12 +02:00
feat: add the control plane for mailbox sync fair use, so a mailbox syncs under an operator-editable policy and its progress survives worker replacement: a sync section on the instance settings document (backfill window in days, backfill cap per mailbox, daily new-mail budget per mailbox and per organization, each clamped on read and write with compiled defaults in constants.go), models.SyncPolicy and models.SyncState with a provider-shaped jsonb SyncCursor, a new email_sync_state table plus an index on tasks.message_id that the reply lookup was scanning sequentially without, an EmailSyncStateRepository whose Put also stamps email_accounts.last_synced_at which nothing had written since the baseline so every admin and dashboard Last synced surface read NULL, an OrganizationID and Sync block on the ADD_EMAIL payload resolved by the loader from instance settings and the saved state and, for IMAP, the saved unibox_mailboxes folder cursors that the loader had never populated so every worker restart re-walked every folder from scratch, a SYNC_STATE consumer handler that persists the relay and publishes ACCOUNT_SYNC_STATE plus a warning when the import completes or fair use flips, an internal own-conversation endpoint the worker's priority lane asks whether a new message replies to a campaign task, a mapped message or a stored thread, GET /emails/:id/sync for the dashboard, and SYNC_FLOOD and SYNC_FAIR_USE mail error codes with user copy for the two patterns that deactivate a mailbox
2026-08-18 08:43:20 -07:00
2026-06-11 09:30:21 +02:00
2026-08-13 09:47:46 +02:00
2026-06-11 07:47:29 +02:00
2026-06-02 15:54:29 +02:00
2026-07-13 20:11:21 +02:00
2026-07-20 08:01:22 +02:00
2026-07-16 08:57:19 +02:00
2026-07-19 18:45:50 +02:00
2026-08-18 07:53:39 -07:00
2026-06-11 11:32:57 +02:00
feat: make self-hosted auth work without a mail relay by rewriting the platform SMTP transport with real AUTH and TLS (it did neither, so SMTP_USERNAME/SMTP_PASSWORD were dead and every documented relay was unreachable), adding MAIL_TRANSPORT=smtp|log|ses with a log transport that prints codes so a fresh install can sign in with no relay, demoting the emailed login code to AUTH_LOGIN_CODE=always|new_device|off (off on self-host, per NIST SP 800-63B and OWASP ASVS), claiming the first owner through a single-use setup link or WARMBLY_BOOTSTRAP_* instead of register-then-psql, deriving every emailed URL from APP_URL rather than a hardcoded app.warmbly.com that leaked live reset tokens to the vendor, fixing the confirm hooks that read path params against paramless routes and broke login, register and reset confirmation in the dashboard everywhere, adding generic OIDC with PKCE, one-time state, verified nonce and (issuer,subject) identity binding, enforcing 2FA on the social paths that skipped it, adding a per-IP limiter and trusted-proxy handling to the unthrottled auth group, refusing boot on the published default secrets, and dropping mailpit from the default stack (#99)
2026-08-14 14:57:09 +02:00
2026-06-01 03:09:17 +02:00
2026-06-03 16:47:53 +02:00
2026-07-20 09:56:02 +02:00
2026-06-28 05:09:50 +00:00
2026-06-30 10:06:20 +02:00
2026-06-01 05:53:49 +02:00
2026-04-03 06:08:52 +00:00
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
2026-07-13 17:40:20 +02:00
2026-06-11 07:47:29 +02:00
2026-06-03 06:27:07 +02:00
2026-06-01 04:23:00 +02:00
2026-06-12 09:38:11 +02:00
2026-06-08 15:04:47 +02:00
2026-01-17 14:11:14 +00:00
2026-07-20 05:28:05 +02:00
2026-08-16 07:46:16 +02:00
2026-06-03 16:47:53 +02:00
2026-06-03 05:05:53 +02:00
2026-06-01 04:23:00 +02:00
2026-01-26 04:42:19 +01:00
2026-07-11 17:31:23 +02:00
2026-01-29 05:59:04 +01:00
2026-01-26 04:42:19 +01:00
2026-06-15 08:11:20 +02:00
2026-05-30 09:37:26 +00:00