Releases: incredible-zetta/crm
Release list
v0.7.1 — fix x_tweet account label
Fixes
x_tweetaccount label resolution:x_tweet(tweet detail) calledTweetDetailwith the rawin.Cookiesfield, skippingresolveCookies. Passingaccount=<label>(no inline cookies) failed withx_tweet: cookies requiredeven for a stored, live account. It now resolves cookies like every otherx_*tool (label precedence →CookiesForLabel, else inline blob), supporting both stored account labels and inline cookies.
Audited all x_* handlers: x_tweet was the only affected tool. x_account_save intentionally keeps the raw blob.
Full changelog: v0.7.0...v0.7.1
v0.7.0 — x.com (Twitter) MCP integration
Features
x.com (Twitter) MCP integration — full multi-account channel ported in-process from x-utils (cookie auth, no shell-out):
- 13 core
x_*tools:x_user,x_identify,x_post,x_delete,x_search,x_user_tweets,x_tweet,x_replies,x_followers,x_following,x_dm, plus account + watch management. - Account persistence + liveness (
x_account_save/list/delete): accounts keyed(tenant_id, label); cookies write-only (never returned/logged); liveness cron. - Contact social handles:
x_username/threads_usernameon Contact. x_replies: top-level reply tweets per conversation thread, paginated.- Signed-webhook watches (
x_watch_save/list/delete/events): per-watchwebhook_url+ rotatablewebhook_secret+ custom headers; HMAC-SHA256X-Zetta-Signature; exportedSignWebhook/VerifyWebhookSignature; poller cron. x_identify: acting-account self summary.threads_daily_summaryMCP tool.
Fixes
- Liveness +
x_identifyself-resolution: resolve the acting account's own profile via twid →UserByRestIdinstead of a storedscreen_name+ invalidfrom:mesearch fallback. Fixes functional accounts being wrongly markeddeadand letsx_identifywork from raw cookies.
Migrations
0011_x_accounts, 0012_contact_social_handles, 0013_x_watches.
Security
cookies/webhook_secretare sensitive: never logged/echoed; MCP outputs exposehas_secretonly.- Webhooks HMAC-signed; built-in headers override agent-supplied ones so security headers can't be clobbered.
Full changelog: v0.6.0...v0.7.0
v0.6.0 — Optional multi-tenancy
Optional multi-tenancy
Adds opt-in multi-tenancy controlled by the MULTI_TENANCY env flag. Default is false, which preserves the original single-tenant behavior exactly.
When enabled
- Identity =
(Authorization key, X-Session-Id). Auth key sent as today (Authorization: Bearer/X-API-Key); the tenant additionally sends a static, client-controlledX-Session-Idheader. - Auto-provisioning, no admin. First time a
(key, session_id)pair is seen, a tenant is created and an opaque, stabletenant_idis derived. Raw key never stored (only SHA-256 hash). - Data scoping. Every owned table has a
tenant_idcolumn; all queries filter by the tenant resolved from context. Name/natural-key uniqueness is per-tenant (email, template name, mailbox, chat, external object ids). - Channel config stays global. WhatsApp / Threads / IMAP gateway + tokens are shared via env; only the data is scoped.
- Public routes (
/t,/o,/export,/u, wa webhook) resolve the owning tenant from the globally-unique random code/id on the matched row. - Background jobs record the owning tenant per task and re-inject it into context before tenant-scoped work.
Migration
0010_multi_tenancyadds thetenantstable, atenant_idcolumn on every owned table (default'default'), and per-tenant composite unique keys. Existing rows backfill to'default'. Auto-applied on startup; no manual steps.
Backward compatibility
Verified on a real MySQL upgrade (v9 → 0010): legacy single-tenant data backfilled and fully reachable, multi-tenant isolation verified, full integration suite green.
Config
| Variable | Default | Notes |
|---|---|---|
MULTI_TENANCY |
false |
true scopes all data per tenant; tenants auto-provisioned from (Authorization, X-Session-Id). |
PR #23.
www.threads.com_cookies.txt session file was briefly committed during merge and has been purged from history via a force-push rewrite. If you have a local clone from before this release, re-clone or hard-reset to the new master. Rotate any affected Threads/Instagram session.
v0.5.0
Features
- Add Threads hybrid channel
- Support Threads topic tags
- Expand Threads search params and add token tools
- Add threads_reply_tree and fix threads_history serialization
Fixes
- Flatten threads_reply_tree output to avoid MCP schema cycle
- Address Threads CI lint and docs
Other
- Merge Threads hybrid channel PRs
- Merge Threads reply tree PRs
v0.4.0
WhatsApp channel (two-way messaging, smart-send, delivery tracking); campaign scheduling/status/lead-stage fixes; GHCR master pre-release builds. Full changelog: v0.3.0...v0.4.0
v0.3.0
Features
- Added self-hosted email verification: RFC syntax check, DNS MX lookup (A/AAAA fallback), and disposable/role-address heuristics. No SMTP RCPT probe (unreliable, reputation-damaging).
- Each contact now carries
email_status(valid/invalid/risky/unknown),email_reason, andemail_checked_at. - Verification hooks: on create, on update (when email changes), on audit, and a send guard.
- New tool
email_verify— verify one contact and persist the verdict. - New tool
email_audit— sweep a segment; async by default (returnstask_id, runs via the scheduler over the whole segment),sync:truefor a single inline page. - New env:
VERIFY_EMAILS(verify on create/update),BLOCK_INVALID_SEND(refuse sending to contacts verified invalid).
Usage
{"name":"email_verify","arguments":{"id":42}}
{"name":"email_audit","arguments":{"segment":{"stage":"new"},"only_unchecked":true}}
{"name":"email_audit","arguments":{"sync":true,"limit":200}}Notes
- Verification is self-hosted and free;
validmeans deliverable-capable, not guaranteed-inbox. - Large audits run asynchronously through the in-process scheduler, consistent with
campaign_send.
Verification
make checkstaticcheck ./...
v0.2.0
Features
- Added
contact_bulk_update— apply a partial patch to many contacts by ID list (max 500 per call). Returns{matched, updated, skipped, errors}. - Added
contact_bulk_update_by_filter— apply a partial patch to every contact matching a segment filter (stage,company,tag,q); pages internally. - Tag semantics are agent-friendly:
add_tags/remove_tagsmerge without clobbering, orset_tagsoverwrites the whole list. - Partial patch fields:
company,stage,notes,source, plus tags. Omitted fields are left unchanged.
Usage
{"name":"contact_bulk_update","arguments":{"ids":[1,5,9],"patch":{"stage":"qualified","add_tags":["vip"]}}}
{"name":"contact_bulk_update_by_filter","arguments":{"segment":{"stage":"proposal"},"patch":{"add_tags":["q3-push"]}}}Notes
- Both tools are synchronous (pure DB, no provider rate limits). Invalid stage, empty patch, or >500 ids return
invalid_input. Missing contacts are skipped and reported inerrors.
Verification
make checkstaticcheck ./...
v0.1.4
Fixes
email_sendnow returns the underlying send failure detail inmsg(e.g. SMTP auth/connection errors) instead of the generic"email send failed", matching thetemplate_renderdiagnostics behavior. The error code stayssend_failed.
Diagnosing send failures
- Sync:
email_sendreturns{error: "send_failed", msg: "send failed: <cause>"}. - Async campaigns: the real cause is recorded in the task
last_error; inspect viatask_list.
Verification
make checkstaticcheck ./...
v0.1.3
Features
- Added outbound email throttling to respect provider rate limits. Set
EMAIL_RATE_MAXandEMAIL_RATE_WINDOW_SEC(e.g.200/100for Larksuite) to pace delivery. - The limiter is a context-aware token bucket wrapping the email sender, applied to both single
email_sendandcampaign_send. Campaign loops now block until a slot frees instead of bursting the SMTP provider. - Leave the env vars unset to send without throttling (unchanged behavior).
Usage
EMAIL_RATE_MAX=200
EMAIL_RATE_WINDOW_SEC=100Verification
make checkstaticcheck ./...
v0.1.2
Features
campaign_sendnow enqueues a background task by default and returns immediately with{campaign_id, task_id, status: "queued"}, avoiding MCP request timeouts on large segments. The in-process scheduler dispatches it and flips the campaign tosent/failed.- Added
sync: trueescape hatch oncampaign_sendfor small/test sends that wait inline and return live counts. - Campaigns are marked
sendingup front so pollers see progress; failed sends transition tofailedinstead of staying stuck.
Usage
- Async (default):
campaign_send {"campaign_id": 1}→ pollcampaign_statsortask_list. - Sync:
campaign_send {"campaign_id": 1, "sync": true}.
Verification
make checkstaticcheck ./...