diff --git a/docs/content/docs/guides/advisor.mdx b/docs/content/docs/guides/advisor.mdx index 355cb7f73..128ab11f5 100644 --- a/docs/content/docs/guides/advisor.mdx +++ b/docs/content/docs/guides/advisor.mdx @@ -3,10 +3,58 @@ title: "Advisor" description: "Warmbly watches your sending and tells you what to fix, on the page where the fix lives." --- +The Advisor checks your workspace for problems that quietly hurt cold outreach, such as climbing complaints or a domain missing DMARC. It says what is wrong and what to do, on the page where the problem is. + +## Fix a finding + + + + + +Click the colored flag on a mailbox or campaign row to open its findings. + + + + + +Read **Why** to see the measured numbers behind the finding. + + + + + +Check **What changes**, which shows the exact before and after of every field. + + + + + +Apply the fix. **Done** offers **Undo** where the change can be reverted. + + + + + +## Good to know + +- Detection is explicit rules with explicit thresholds, not AI, so it works with no AI configured and never spends AI credits. +- A check stays quiet until it has enough data, for example `100` sends in 30 days for complaint rate. +- Nothing is applied until you have seen the **What changes** screen, and the change runs with your permissions. +- Autopilot, under **Settings > Workspace**, is off by default and only takes bounded fixes that can be undone. +- **Snooze** hides a finding for a week or a month. **Stop suggesting this** dismisses it until the problem clears and recurs. + + + +## What the Advisor does + The Advisor checks your workspace for the things that quietly ruin cold outreach: complaints climbing, a domain missing DMARC, a new mailbox at full volume, a list that is mostly shared inboxes. It says what is wrong, why it matters, and what to do, and where the fix is a setting, it offers to make the change. Advice appears on the page where the problem is, not in a separate inbox you have to check. + + + + ## Where you see it | Surface | What appears | @@ -21,6 +69,10 @@ A step's copy problem flags its campaign, since a step has no row. When there is If one check fires on many things, they collapse into a single card ("19 mailboxes are capped above the safe band") that expands to list each affected row with its own **Fix**, plus one **Fix all**. + + + + ## How findings are made **Detection is not AI.** Every check is an explicit rule with an explicit threshold run over your own data, so the same numbers always produce the same result. AI only rewrites the explanation to read more naturally; it cannot create a finding, change its severity, or change what a fix does. The Advisor therefore works fully on a self-hosted install with no AI configured and never spends AI credits. The wording is just plainer. @@ -48,6 +100,10 @@ A check stays quiet until it can be right. Two bounces in thirty sends is not a Only the first two badge a nav tab. + + + + ## What it checks **Deliverability**: complaint rate per mailbox (warns `0.03%`, critical `0.10%`, matching Google's bulk-sender ceiling); bounce rate (warns `3%`, critical `5%`, where SES opens a review); spam placement on warmup mail at Google, Microsoft and Yahoo (warns `10%`, escalates `20%`, critical `50%`; other mail hosts are not counted, see [how spam placement is judged](/guides/warmup/#how-spam-placement-is-judged)); SPF and DMARC per sending domain (critical while sending cold, since Google requires them; an unchecked domain is never called failing, and DKIM is never reported missing because its selector is not discoverable from DNS); and whether a mailbox has its own verified tracking domain. @@ -64,6 +120,10 @@ Two more copy findings run only when the operator has configured TypeSafe (`TYPE **List quality**: shared inboxes (`info@`, `sales@`) above `15%`; consumer domains above `40%` of a B2B list; suppressed contacts above `10%`; missing first names above `10%` when copy personalizes on them; unsubscribed contacts still enrolled. + + + + ## Fixing something Opening a recommendation walks three screens: **Why** (with the measured numbers), **What changes** (the exact before and after of every field), and **Done** (with **Undo** where revertible). @@ -105,18 +165,28 @@ Under **Settings > Workspace**, off by default. It only takes fixes that are bou **It never pauses sending, edits copy, starts or resumes warmup, or acts without an undo.** Eligible findings are marked **Auto** on the card whether or not it is on, so the set is visible before you enable it. It acts as the member who enabled it, with their permissions, logged as them, and stops if that person leaves. At most ten fixes per evaluation. + + + + ## Making one go away - **Snooze** hides it for a week or a month; the check keeps running. - **Stop suggesting this** dismisses it until the problem clears and recurs later. - **Useful / not useful** feeds threshold tuning. + + + + ## Settings and timing Under **Settings > Workspace** you can switch the Advisor off, enable autopilot, mute whole categories, or raise the minimum severity. Changing these needs the manage-settings permission and applies workspace-wide. Findings refresh in the background and when you open a page whose advice went stale. Adding, changing or removing a mailbox re-checks them right away, and removing a mailbox, campaign or step closes the advice about it in the same action. Updates are live, so a teammate applying a fix clears the card on your screen. + + ## See also - [Deliverability](/guides/deliverability/) for the underlying measurements and health bands. diff --git a/docs/content/docs/guides/ai-assistant.mdx b/docs/content/docs/guides/ai-assistant.mdx index 9de82e04f..ec31495b5 100644 --- a/docs/content/docs/guides/ai-assistant.mdx +++ b/docs/content/docs/guides/ai-assistant.mdx @@ -3,6 +3,44 @@ title: Remie, the AI assistant description: Remie, Warmbly's in-product AI assistant, searches your data, edits records, sends messages with your approval, and manages campaigns, mailboxes, and settings, never doing anything your own role could not. --- +Remie is Warmbly's AI assistant: a chat panel in the dashboard that reads across your workspace and takes almost any action you could take yourself. It asks before it changes anything. + +## Ask Remie + + + + + +Open Remie from the blue blob in the top bar, or press `Cmd/Ctrl + I`. + + + + + +Ask in plain words. It knows what page you are on, so "summarize this campaign" works. + + + + + +Choose **Approve** or **Skip** on each approval card Remie shows before a change. + + + + + +## Good to know + +- Remie needs a paid plan and the **Use AI** role permission. +- It acts as you: only what your role allows, only in your workspace. +- Sending, starting things that send, and changing who has access always ask, even with **Always allow** on. +- Conversations are private to you unless an admin turns on **Shared history**. +- Each step costs one AI credit, and the stop button ends a run anytime. + + + +## What Remie is and who can use it + Remie is Warmbly's AI assistant: a chat panel in the dashboard that reads across your contacts, campaigns, mailboxes, unified inbox, CRM, team, and settings, and takes almost any action you could take yourself. Answers stream word by word and each tool step appears as it starts. Open Remie from the blue blob in the top bar or `Cmd/Ctrl + I`. The panel follows you between pages. @@ -15,6 +53,10 @@ Remie acts as you: only what your role allows, only in your workspace, never ano Remie needs a paid plan; a free trial has no credit allowance and the AI surfaces stay locked. Beyond that, access is the **Use AI** role permission (on for admins and managers, off for viewers); without it Remie and AI drafting are hidden. Admins can cap per-member spend by day, week, or month. See [AI credits](/guides/ai-credits/) and [Team and roles](/guides/team-roles/). + + + + ## Conversations and the panel Conversations are **private to you by default**. An admin can enable **Shared history** under Settings > Workspace > Remie, after which every member with Use AI sees, opens, continues, and deletes any conversation, attributed by owner. Turning it on exposes existing conversations, so decide deliberately. Delete individually from the history rail, or **Clear history** to remove every conversation you own. @@ -34,6 +76,10 @@ Docked, the panel resizes by dragging its inner edge, which also takes the keybo **Work continues without the panel.** Minimizing docks it to a status bar showing live state: working with how long the run has taken, waiting on approval, or holding an unread response. Closing it entirely also keeps runs going, and the blob in the top bar shows the state: it swirls while Remie works, turns amber when an approval waits, flashes green when a run finishes, and carries a dot while a response is unread. + + + + ## Suggestions and tips Remie reads what the [Advisor](/guides/advisor/) measures in your workspace and suggests fixes for it, never generic prompts. An empty conversation lists them under **Suggested for you**, one line per kind of problem ("3 new mailboxes are sending at full volume"). Choosing one asks Remie to fix it: it checks the current state, proposes the smallest change, and shows each one as an approval card first. @@ -48,6 +94,10 @@ Now and then Remie also offers one of these in a small bubble under the blob in These preferences are yours alone, kept in your browser for each workspace. + + + + ## What you can ask Remie - Find, read, create, and delete contacts, edit fields, labels, subscription, and notes, and bulk-edit @@ -66,6 +116,10 @@ These preferences are yours alone, kept in your browser for each workspace. It knows what page you are on, so "summarize this campaign" and "draft a reply here" resolve correctly. + + + + ## Approvals **Reading runs automatically.** **Changing always asks**, showing an approval card with the action and every argument Remie passed (expand **All arguments** to read them). You can **Approve** that one action or **Skip** it. A member with **Manage settings** can also choose **Always allow**, which lets that kind of action run without asking for everyone in the workspace from then on; other members approve one action at a time. @@ -87,8 +141,14 @@ Settings > Workspace > Remie lists every action that is always allowed, who allo **Drafts stay drafts.** Campaigns Remie builds are never started and automations are left disabled; you get a button to open them in the real editor. Copy it writes is grounded in your workspace [voice profile](/guides/unibox/) and follows the same human-writing rules as the rest of Warmbly. + + + + ## Credits and privacy Each step costs one AI credit, and a request is capped at a set number of steps (shown as the run progresses) so nothing runs away. Remaining credits show at the panel's bottom, and the stop button ends a run anytime. On a self-hosted install with a free or local model, runs are not charged and a "free model" notice appears. Changes Remie makes (a created task, an updated contact) appear in the shared activity feed like any other edit, so teammates' views stay current. + + diff --git a/docs/content/docs/guides/ai-credits.mdx b/docs/content/docs/guides/ai-credits.mdx index 545a0ff58..1e1562314 100644 --- a/docs/content/docs/guides/ai-credits.mdx +++ b/docs/content/docs/guides/ai-credits.mdx @@ -3,12 +3,54 @@ title: AI credits description: How AI credits work in Warmbly, what each AI action costs, the monthly allowance and reset rules, and how to buy top-up packs. --- +AI features run on credits, and they need a paid plan. Your plan grants a monthly allowance that refreshes each billing cycle, and top-up packs you buy never expire. + +## Check and top up credits + + + + + +Open the **AI & credits** tab of **Settings > Billing** (it needs the **Manage billing** permission). + + + + + +Check your balance and the reset date beside it. + + + + + +Buy a top-up pack of **500**, **2,000** or **10,000** credits through Stripe Checkout. + + + + + +## Good to know + +- Unused monthly credits do not roll over. Purchased credits survive every reset. +- Spending draws the monthly allowance first, then purchased credits. +- Each action reserves a small minimum up front, then settles against real usage. A provider error refunds it. +- Running out stops work before it starts, so you are never half-charged. +- You can set daily, weekly and monthly spend limits for the workspace and for each member. + + + +## What credits are and who manages them + AI features run on credits, and they are paid-only: a free trial carries no allowance and the AI surfaces stay locked until the workspace is on a paid plan. Your plan grants a monthly allowance that refreshes each billing cycle, and top-up packs never expire. Everything here lives on the **AI & credits** tab of **Settings > Billing** and needs the **Manage billing** permission. Other members do not see it. + + + + ## The two pools A dedicated reviewer workspace starts with 100 test credits. Its header gauge and popover label them **Test credits** rather than plan credits. These use the normal credit ledger and spend rules, and do not keep paid features unlocked after the reviewer grant expires. Joining an existing workspace as a tester does not add credits. See [reviewer accounts](/development/accounts-and-access/#which-workspace-a-tester-lands-in). @@ -20,6 +62,10 @@ A dedicated reviewer workspace starts with 100 test credits. Its header gauge an Spending draws the monthly pool first and only touches purchased credits once it is empty, holding your top-ups in reserve for heavy months. + + + + ## What each action costs Each action reserves a flat **minimum** up front (which also gates an empty balance before work starts), then settles against real token usage after the call. @@ -46,10 +92,18 @@ Five rules govern the settle: - A contact research run that finds nothing still costs its credits. You are paying for the search, not the result. - Running out returns "insufficient credits" **before** any work starts, so a depleted balance never leaves you half-charged. + + + + ## Placement tests Credits also pay for [inbox placement tests](/guides/placement-tests/#free-tests-and-credits) once a paid workspace has used its free tests for the month. A test is a flat `25` credits (the operator can change the price), with no AI involved and no settle afterwards. Nothing is charged without you agreeing to it: the new-test dialog shows the price and your balance, and you tick to pay before the test can start. A test that delivers no copy is refunded to the pools it came from. Placement charges count toward your spend limits (a refunded one no longer does) and appear in the usage breakdown as **Placement tests**; they are not held to the AI generation caps. Tests on your own seed inboxes are always free. + + + + ## Usage and spend controls The **AI usage & spend controls** card shows today, this week, and this month spend, a graph over the last 7, 30, or 90 days, and breakdowns by feature and model (credits, tokens, call counts). Each limit is set by slider or exact number and shows current spend against it; changes collect into a save bar. @@ -63,6 +117,10 @@ The **AI usage & spend controls** card shows today, this week, and this month sp Who can use AI at all is separate: the **Use AI** role permission (see [Team and roles](/guides/team-roles/)). + + + + ## Allowances and top-ups | Plan | Monthly credits | @@ -78,10 +136,18 @@ Paid workspaces get a fresh grant each cycle, with the reset date shown beside y Credits arrive only after Stripe confirms payment, not when you click. Closing the checkout tab charges nothing. A completed purchase is applied exactly once even if Stripe retries. + + + + ## The transaction log Every grant, purchase, and spend, newest first, with the activity, model and token count, signed credit change, resulting balance, and time. Each spend records exactly what ran it: campaign, step, and contact for a switch step; automation, node, and run for an automation step (including what an Ask AI branch asked); the thread for inbox drafts; and the teammate behind manual features. + + + + ## FAQ **Do unused monthly credits roll over?** No. Buy a top-up pack for credits that carry over. @@ -93,3 +159,5 @@ Every grant, purchase, and spend, newest first, with the activity, model and tok **What happens if I cancel?** Monthly grants stop; any purchased balance stays until spent. **Do credits apply when self-hosting?** Not when billing is off, which is the self-host default (`BILLING_PROVIDER=none`). You pay your AI provider directly, so the credit ledger is bypassed: nothing is debited, there is no plan allowance, the header shows no credit gauge, and per-action cost hints are hidden. A self-hosted install that runs Stripe (`BILLING_PROVIDER=stripe`) is metered exactly like the hosted product. Separately, a free or local model (`AI_PROVIDER=ollama`, or any provider with `AI_FREE=true`) is never charged and shows a "free model" notice. See the [self-hosting guide](/development/deployment-guide/). + + diff --git a/docs/content/docs/guides/ai-steps-in-automations.mdx b/docs/content/docs/guides/ai-steps-in-automations.mdx index 4032187a9..81b1c1bb0 100644 --- a/docs/content/docs/guides/ai-steps-in-automations.mdx +++ b/docs/content/docs/guides/ai-steps-in-automations.mdx @@ -3,8 +3,56 @@ title: AI steps in automations description: Add an AI step or an AI switch to an automation so a flow can read the event with a model, take reversible actions, or route down one of your cases. --- +AI steps let an automation read an event with a model and act on it, or route it down one of your cases. They are built in and need no integration. + +## Add an AI step + + + + + +In an automation, add an action node. + + + + + +Under **Run**, choose **Warmbly (built-in)**, then **AI step** or **AI switch**. + + + + + +Write a plain-language instruction. Drop in event fields with `{{.field}}`. + + + + + +Press **Test** to see the real output. AI steps run for real in a test and spend credits. + + + + + +## Good to know + +- No AI step ever sends email or contacts anyone. +- Classify, extract and generate cost 1 credit; the agent costs 1 per step it takes. Provider failures refund automatically. +- Out of credits, the step fails and the run continues down the normal path. +- **Ask AI (yes/no)** fails safe: if the model can't answer, the **no** path runs. +- An automation whose AI steps keep failing for lack of credits is switched off. + + + +## The two AI nodes + Automations have two AI nodes, mirroring the AI steps in campaign sequences: an **AI step** that reads the event and transforms or acts on it, and an **AI switch** that routes it down one of your cases. Both are built-in, needing no integration. Add one from an action node: **Warmbly (built-in)** under **Run**, then **AI step** or **AI switch**. + + + + ## The AI step Every mode takes a plain-language **instruction**, templated so you can drop event fields in with `{{.field}}`. The step also sees the rest of the event data, so you need not list every field. @@ -30,6 +78,10 @@ On a Reply received trigger, an instruction like "If they ask about pricing, lab A generate step only writes text into a variable. To use it you connect a later step you control (a Slack message, a task, a webhook). Warmbly never sends outreach email from an AI step. + + + + ## The AI switch Routes the event down exactly one of your **cases** (at least two), each its own column under the switch. Put ordinary action steps under a case to make things happen for events routed down it. Steps under **Always** run whichever case is picked; a renamed case keeps the steps under it. @@ -41,12 +93,20 @@ Routes the event down exactly one of your **cases** (at least two), each its own An unmatched value, or a model picking no case, takes the fallback. + + + + ## Capabilities Both nodes can enable **extended thinking**, routing to the stronger model with a larger reasoning budget, metered by usage rather than a flat credit. The AI switch in prompt mode can also enable **web search**: it looks up the event's company before deciding. The query is built only from event fields such as company name or corporate email domain, **never from the reply text**, and costs one extra credit only when it returns results. + + + + ## The Ask AI branch A condition node can ask the model directly. Pick **Ask AI (yes/no)** and write a question about the event ("Is this reply asking about pricing?"): yes follows the Yes column, no the No column. @@ -56,6 +116,10 @@ A condition node can ask the model directly. Pick **Ask AI (yes/no)** and write - **Fails safe**: if the model can't answer, you are out of credits, or the answer is ambiguous, the **no** path runs and the reason is recorded. - Use an AI switch when you need more than two outcomes. + + + + ## Using the result downstream Classify, extract, and generate write into the flow exactly like a **Set variables** step, so every later step can read them: @@ -65,6 +129,10 @@ Classify, extract, and generate write into the flow exactly like a **Set variabl If two AI steps would both write `ai_class` or `ai_text`, set an **output variable name** on one so they do not overwrite each other. + + + + ## Cost, failures, and safety | Topic | Behavior | @@ -75,10 +143,16 @@ If two AI steps would both write `ai_class` or `ai_text`, set an **output variab | Timing | Each step is bounded to a few seconds so a slow model never stalls the flow | | Sending safety | **No AI step ever sends email or contacts anyone.** The agent takes only the reversible actions you enable; other modes only write a variable | + + + + ## Testing **Test** dry-runs the flow, but AI steps and Ask AI branches run **for real** so you see actual output and the branch it drives, which spends credits. The agent reports what it would do without applying it. The trace panel shows each step's output beside its node. See [Testing and history](/guides/automations/). + + ## See also - [Automations](/guides/automations/) for the builder, triggers, and conditions. diff --git a/docs/content/docs/guides/analytics.mdx b/docs/content/docs/guides/analytics.mdx index db66ef6b2..5139daf99 100644 --- a/docs/content/docs/guides/analytics.mdx +++ b/docs/content/docs/guides/analytics.mdx @@ -3,12 +3,62 @@ title: "Analytics" description: "Campaign and account performance metrics." --- +Analytics turns every send, open, click, reply, bounce and complaint into rates you can act on, for the whole workspace, each campaign and each mailbox. + +![The workspace dashboard: sends, opens, replies and bounces over time, top campaigns and account health](/guides/analytics.webp) + +## Read your numbers + + + + + +Check the four headline rates for your window: `7d`, `30d` or `90d`. + + + + + +If one looks off, switch the chart to that metric and find the day it moved. + + + + + +Open that campaign and read its per-step stats to see which touch is responsible. + + + + + +For bounces or complaints, check the per-mailbox breakdown to find the sender at fault. + + + + + +## Good to know + +- Opens and clicks only count when open or link tracking is on for the campaign. +- Bots, security scanners and privacy-proxy auto-opens are logged but never counted. +- Replies are human replies. Out-of-office notices and autoresponders never count. +- A click by a person counts as an open too. +- Warmbly will not call an A/B winner until the leader has a minimum sample, `30` sends by default. + + + +## What analytics covers and who can see it + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. Analytics rolls every send, open, click, reply, bounce, and complaint into rates you can act on, by workspace, campaign, and mailbox. For health bands and the thresholds Warmbly acts on, see [Deliverability](/guides/deliverability/). Campaign analytics are shared across the workspace. With **View analytics**, you can see the overview, daily and hourly statistics, step performance and comparisons for campaigns created by any member of the selected workspace. The campaign author does not need to grant separate access. + + + + ## Signup source attribution (platform admins) This is separate from email and campaign analytics. The marketing site carries the original landing path, external referrer host and any `utm_source`, `utm_medium`, `utm_campaign`, `utm_term` and `utm_content` tags through its page links into the dashboard. App redirects and switching between sign-in and registration keep those fields available at signup. @@ -19,6 +69,10 @@ Sources come from campaign tags or the browser's referrer, not guesses. Warmbly' The marketing site stays cookieless, with no persistent PostHog identity. Attribution travels in URLs, not cookies or browser storage. It survives linked navigation but cannot reliably reconnect a later unrelated visit, another device or a URL whose attribution parameters were removed. It is not a complete anonymous browsing history. Existing saved acquisition remains unchanged, and missing historical sources are not backfilled. No new configuration or database migration is required. + + + + ## What is counted | Metric | Counts | Feeds | @@ -41,6 +95,10 @@ Seven rules govern the counts: - **A click is an open.** Opens need the mail client to load images, and many clients block them. A click by the person proves the email was read, so it counts as an open as well, and a lead can never read "clicked, not opened". - **Auto-clicks are kept but never counted.** A click classified as automated is logged on the contact's activity with the link it hit and the rule that caught it, and the campaign overview shows how many steps had only automated clicks (`3 auto`). It never makes the step clicked, never fires a clicked branch or automation, and never sends a webhook. A person clicking the same link later counts normally. A burst is only recognisable from its second click, so a click that looks human waits for the burst window plus one second (six seconds) before it fires anything (clicked branches, automations, webhooks, the live feed): if a second link follows inside that window, both clicks are relabeled, the stamp is withdrawn and nothing fires. + + + + ## Workspace dashboard Live, scoped by `7d`, `30d`, or `90d` tabs, and by default covering every campaign in the workspace. @@ -72,6 +130,10 @@ The **All campaigns** menu next to the period tabs narrows the dashboard to the A campaign or folder that is deleted while selected drops out of the filter on its own. The mobile app's Analytics overview has the same menu under its period picker; there the filter changes the overview's campaign figures and leaves the Deliverability, Warmup and Accounts sections as they are. Through the API, pass `campaign_ids` and `folder_ids` to [`GET /analytics/dashboard`](/api/reference/analytics/#get-dashboard-analytics). + + + + ## Direct mail The rest of this page measures campaigns. **Direct mail** measures what you write by hand in the [unified inbox](/guides/unified-inbox/), which most workspaces do far more of than they expect. @@ -95,6 +157,10 @@ To switch it on, open the mailbox and turn on **Track opens and clicks on direct **Per mailbox** ranks your mailboxes by how many messages each mailbox sent and received, and marks the ones that are tracking. **Top correspondents** ranks the people you exchange the most mail with. Your own mailboxes are excluded from that list, so a copy to yourself is not a correspondent. + + + + ## Per-campaign Each campaign reports its own totals: enrolled contacts, sent email steps, remaining planned email-step sends, unique opens and clicks, replies, positive replies, interested leads, bounces, unsubscribes, and the five rates. Wait and action steps do not count as messages. @@ -138,6 +204,10 @@ A click names the browser the link opened in and the device it opened on, becaus These records back the engagement rows on a contact's [activity timeline](/guides/contacts-crm/#activity-timeline) and the per-campaign breakdown above. + + + + ## Per-mailbox | Signal | What it tells you | @@ -151,6 +221,10 @@ The overview calls the configured percentage **Reply target** because it control Use this to find the one mailbox dragging down a campaign's bounce rate instead of blaming the campaign. + + + + ## A/B analysis Variants can be scoped to one sequence step or the whole campaign. Traffic splits across the active arms, and the step's own email is the **Original** control arm, itself a first-class arm with its own share, so 70% Original against 30% of a new subject is a valid test. Drag the split bar or type exact percentages; shares always total 100%. @@ -174,6 +248,10 @@ The default winning rule is **reply rate**; you can rank by open or click rate i A variant can look like the winner after a handful of sends and flip later. Wait for `medium` or `high` before promoting one. The minimum sample size is configurable in outreach settings. + + + + ## How to read your numbers 1. Check the four headline rates for your window. @@ -184,6 +262,8 @@ A variant can look like the winner after a handful of sends and flip later. Wait A clean reply rate with low bounces and complaints beats a big open-rate screenshot every time. + + ## Related guides diff --git a/docs/content/docs/guides/automations.mdx b/docs/content/docs/guides/automations.mdx index d6ac76e7c..1340c8ae1 100644 --- a/docs/content/docs/guides/automations.mdx +++ b/docs/content/docs/guides/automations.mdx @@ -3,8 +3,62 @@ title: Automations description: "A visual flow builder: a trigger event connected to action steps across your integrations." --- +An automation does work for you when something happens: a reply arrives or a meeting is booked, and Warmbly posts to Slack, pushes to your CRM or labels a contact. You build it on a canvas, with no code. + +![An automation in the builder: a reply trigger, an If / else on intent, then a deal and a task on the Yes branch](/guides/automation-builder.webp) + +## Build an automation + + + + + +Open the **Automations** tab and choose **New automation**. + + + + + +Pick the trigger it should run on, such as **Reply received**. + + + + + +Press **+** to add actions, and an **If / else** wherever the flow should branch. + + + + + +Press **Test** to dry-run it against sample data. + + + + + +Press **Save** and make sure the automation is switched on. + + + + + +## Good to know + +- Nothing takes effect until you **Save**. **Test** saves first, then dry-runs. +- An automation that is off never fires, even from a campaign step. +- A test sends no messages and changes no records, but AI steps run for real and spend credits. +- One failing step never blocks the rest. The failure is recorded in **History**. +- Bulk file imports and sheet syncs do not fire **Contact created**. + + + +## What an automation is + An automation is a flow on a canvas: one **trigger** (a reply arrives, a meeting is booked) runs one or more **actions** (post to Slack, push to your CRM, label a contact), with **IF conditions** in between to branch. No code, though an advanced mode accepts a free-form condition. +![The automations list, with starting templates underneath](/guides/automations.webp) + + + + + ## The builder Open the **Automations** tab. Each automation has a name, an on/off toggle, and a canvas. **Off** never fires, even from a campaign step. @@ -34,6 +92,10 @@ The canvas lays itself out: the **Trigger** sits at the top and every step runs Triggers, conditions, and actions only take effect after **Save**. **Test** saves first, then dry-runs. + + + + ## Triggers | Trigger | Fires when | @@ -64,6 +126,10 @@ Pick the trigger and **save**, and the editor shows a unique URL. Anything that - The request returns immediately and the flow runs in the background, so a slow action never blocks the caller. - Keys starting with an underscore are stripped; the body is capped at 1 MB. + + + + ## IF conditions An **If / else** splits into a **Yes** column and a **No** column. Only the matching branch runs; an empty branch just ends. @@ -88,6 +154,10 @@ and (gtf .confidence 0.8) (eq .intent "positive") Comparisons (`eq`, `gt`, `lt`), numeric coercion (`gtf`, `ltf`, `add`, `sub`, `mul`, `div`), logic (`and`, `or`, `not`), and text helpers (`contains`, `lower`) are available. A broken or empty expression is **false**, so a malformed condition never silently passes, and it is validated on save. + + + + ## Actions Each action node picks a **Run** target (an integration, or Warmbly built-in) and an **Action**. @@ -160,6 +230,10 @@ New reply from {{.contact_email}} on {{.campaign_name}} Unknown tokens render empty rather than failing. + + + + ## Testing and history **Test** saves the canvas and dry-runs it against sample data: no messages sent, no records changed. It shows the exact path taken, on the canvas (each step it reached is marked, the rest fade back) and as a list with a preview of each action. AI steps are the exception, running for real so you see actual output, which spends one credit per AI step. @@ -170,6 +244,8 @@ Unknown tokens render empty rather than failing. Runs are best-effort and bounded against loops, so one failing step won't block the rest. The run is still recorded as errored so you can see what went wrong. + + ## Related guides diff --git a/docs/content/docs/guides/billing.mdx b/docs/content/docs/guides/billing.mdx index d833348bf..5c041d2bb 100644 --- a/docs/content/docs/guides/billing.mdx +++ b/docs/content/docs/guides/billing.mdx @@ -3,8 +3,52 @@ title: Plans and billing description: How plans unlock features, how to upgrade from anywhere in the dashboard, and what changes when you switch. --- +Every hosted workspace starts free. A paid plan turns on sending from Warmbly Cloud, the unified inbox, contacts, the CRM, automations and integrations. + +![Settings > Billing: the plan card, usage against limits, and the subscription actions](/guides/billing.webp) + +## Upgrade your plan + + + + + +Open a feature your plan does not include, or choose **Change plan** under **Settings > Billing**. + + + + + +Pick a plan, and monthly or annual billing. + + + + + +With no subscription yet, finish in Stripe Checkout. An existing subscription switches in place. + + + + + +## Good to know + +- The free workspace warms up to 10 mailboxes. +- Annual billing is 20% off the monthly price. +- Only the workspace owner can change the plan. +- Cancelling keeps the plan active until the end of the paid period, and the workspace keeps its mailboxes, warmup and settings. +- A workspace that only warms can take the Warmup plan: $15 a month, or $144 a year. + + + +## What the free workspace includes + Every hosted workspace starts free. The free workspace warms up to 10 mailboxes, connected on the cloud or through a linked self-hosted instance. Sending from Warmbly Cloud, the unified inbox, contacts, the CRM, automations and integrations turn on with a paid plan. A workspace that only warms can take the [Warmup plan](#the-warmup-plan) instead. + + + + ## Plans Dedicated reviewer workspaces show **Test** in the header instead of a customer plan. They have operator-granted, time-limited access, not a purchased subscription. Joining an existing workspace as a tester does not change its plan badge. See [reviewer accounts](/development/accounts-and-access/#which-workspace-a-tester-lands-in). @@ -33,6 +77,10 @@ It is not a self-hosting plan; it is the plan for warmup on its own. Everything It has no card in the plans grid. The ways in are the **Upgrade** button on the **Mailboxes** page, the **Only need warmup?** row on the billing overview of a free workspace, and the **Premium** button on a linked instance's **Settings > Warmbly Cloud** page. Each opens the same dialog: pick monthly or yearly, then checkout. Once active the header badge reads **Warmup**, and the overview shows it like any other plan, with the same cancel and resume controls. Its usage section shows the one limit it has, mailboxes, which reads unlimited. + + + + ## Upgrading from a locked feature When you open something your plan does not include, the dashboard opens a full-screen plan chooser instead of sending you to settings. It names the feature, highlights the plan that unlocks it, and lets you pick monthly or annual billing. @@ -47,6 +95,10 @@ Warmbly is sold to businesses only, so checkout asks for your company's legal na Only the workspace owner can change the plan. Other members see the same comparison with a note to ask the owner. Which roles can do what is covered in [Team and roles](/guides/team-roles/). + + + + ## Managing your subscription **Settings > Billing** is owner only and splits into four tabs, each with its own link. @@ -70,6 +122,10 @@ The portal also lets the owner update the billing address, legal billing name, a The portal requires a billing account created through checkout. If your workspace has never completed checkout, including a free workspace or one with only an operator-granted plan, the payment tab explains that checkout is required first and links to the plan chooser. Portal and cancellation controls only appear when the corresponding billing account or Stripe subscription exists. + + + + ## Plans granted by an operator A workspace can be put on a plan without paying for it. Warmbly calls this a **managed plan**, and it exists for internal workspaces, design partners and support gestures. @@ -84,6 +140,12 @@ A grant is held beside the plan a workspace pays for, never on top of it, so a p One thing a grant does not do: monthly AI credits are granted when an invoice is paid, and a granted plan raises no invoice, so a managed workspace does not receive a recurring credit allowance. Top up its balance directly if it needs credits. + + + + ## Self-hosted deployments A self-hosted instance running without a billing provider has every feature unlocked and no billing page. See [Warmbly Cloud](/guides/warmbly-cloud/) for how a self-hosted instance links to a hosted workspace. + + diff --git a/docs/content/docs/guides/campaigns.mdx b/docs/content/docs/guides/campaigns.mdx index d0e69e0fc..e91a1dce1 100644 --- a/docs/content/docs/guides/campaigns.mdx +++ b/docs/content/docs/guides/campaigns.mdx @@ -3,6 +3,58 @@ title: Campaigns description: "Create and send cold outreach campaigns safely." --- +A campaign sends a sequence of emails to a list of contacts, spread across your connected mailboxes. Warmbly handles the timing, volume limits and mailbox rotation, so sending looks natural and stays safe. + +![The campaigns list, grouped by state, with each campaign tagged and filed in a folder](/guides/campaigns.webp) + +## Set up a campaign + + + + + +Open **Campaigns** and choose **New campaign**. + + + + + +On **Leads**, name the campaign and pick one or more lead lists. + + + + + +On **Emails**, write the first email and any follow-ups. + + + + + +On **Schedule**, choose when it starts, the sending days and hours, the mailboxes to send from and the daily limit per mailbox. + + + + + +On **Review**, check the finish date, then choose **Launch** or **Save as draft**. + + + + + +## Good to know + +- Each mailbox sends at most `50` cold emails a day by default, with at least `600` seconds between two sends. +- A campaign sends only inside its weekly sending windows, in the campaign's timezone. +- A lead's follow-ups go out from the same mailbox that sent their first email. +- Launch checks your list first and refuses one that is known to be largely undeliverable. +- Nothing sends until the campaign launches. Closing the flow with anything filled in saves a draft. + + + +## Sender fields and what you need first + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. A campaign sends a sequence of emails to a list of contacts, spread across your connected mailboxes. Warmbly handles timing, volume limits, and mailbox rotation so sending looks natural and stays safe. @@ -13,16 +65,28 @@ Personalize from the mailbox sending each message with `{{.Sender.Name}}` and `{ You need at least one active mailbox and a contact list. Warm new mailboxes before running cold campaigns from them. See [Deliverability](/guides/deliverability/). + + + + ## Shared workspace access Campaigns belong to the workspace. Members with **View campaigns** can read campaign details, steps and activity logs regardless of who created them. **Manage campaigns** allows members to edit settings, create or edit steps and delete steps. Campaign overview metrics require **View analytics**. See [Team and roles](/guides/team-roles/) for permission details. Select the workspace that owns the campaign before opening it. Creating a campaign does not grant access to it from a different workspace. + + + + ## Campaigns list Campaign names take priority over descriptions in the list. Descriptions use the remaining space and are hidden on small screens. A name that is still too long for the available space is shortened with an ellipsis so the status and actions stay accessible. On devices that support hover, hover over the name to read it in full. The top campaigns list in Analytics also uses the available space for names before shortening them. + + + + ## Create a campaign **Campaigns** > **New campaign** opens a four-step flow. A campaign is a first email and, optionally, follow-ups that stop for anyone who replies. @@ -58,16 +122,28 @@ Follow-ups that are due go out before new leads start, and the total assumes nob An unresolved send from one mailbox does not stop other healthy mailboxes from sending. A follow-up normally uses the address its lead first heard from, but can move to another eligible mailbox when that address is held or cooling. The launch plan and day plan show the hold or cooldown separately from the normal daily limit. An unknown send needs [provider-confirmed resolution](/development/send-safety/#resolving-an-unknown-send); a timeout or missing Sent copy is not proof that it can be retried. + + + + ## Images in the editor Warmbly loads external images through a display-only preview service while you edit. The image must be publicly reachable over HTTPS and 5 MiB or smaller. Images behind a login, private-network images and unavailable images show an unavailable preview. Uploaded images are read directly from the instance's object store. Your original image URLs, image links and background-image styles stay in saved templates and sent emails. Recipients load images directly from those original URLs, without a Warmbly login. Existing templates use the same preview behavior without a migration. This does not change recipient open or click tracking, or the remote-image consent in your inbox. + + + + ## Plain text only **Settings** > **Plain text only** sends the campaign without an HTML part. Recipients get the plain-text body with the mailbox's plain-text signature, and because open pixels and wrapped links need HTML, open and click tracking are off for that campaign whatever their toggles say. Test emails respect it too, so what lands in your own inbox is what recipients get. + + + + ## Senders and rotation Pick mailboxes in **Sending accounts** three ways, and the first two combine: @@ -105,6 +181,10 @@ Two things can happen to a lead's mailbox, and Warmbly treats them differently: Removing a mailbox from **Sending accounts** therefore hands its in-flight leads to the remaining mailboxes as each of them reaches its next step. Adding a mailbox never moves an existing lead; it picks up new ones. + + + + ## Sending limits Warmbly is mailbox-first: safe volume is the sum of each mailbox's budget, not one global limit. Limits stack, and **the smallest always wins**. @@ -125,6 +205,8 @@ Each of those lines carries a breakdown per mailbox: the cap this campaign gives The campaign overview opens with a one-line **Today's sending plan**: how many emails the campaign sends today, worked out through the same gates a real send goes through, and the one limit that decides it. **Why** opens the working: a list of every limit that took the number below what your settings add up to, the leads, and each mailbox's day. The card re-reads itself once a minute while the page is open. +![A running campaign's overview: today's sends, performance over time and the live activity feed](/guides/campaign-overview.webp) + For an active campaign the plan is worked out in the background and refreshed about once a minute, so opening it is quick no matter how many leads the campaign has. The card shows when the figures were last worked out. Right after you edit, start or stop a campaign, or just after midnight UTC, it shows the last good figures while a fresh plan is worked out, then updates on its own within moments. A campaign you have only just created is worked out the moment you open it. The number is the sends already out plus what is still expected, and the list under it adds up: the mailbox caps added together, minus each limit, minus what has been sent, is what is still to go. A limit only appears when it removed something. In the order the scheduler applies them: @@ -258,6 +340,10 @@ Every sent count counts emails only: the contact's **Sent** total, a lead's sent No contact is emailed the same step twice. Each step is recorded as attempted before the send is handed to a worker, so a crash, a restart, or a database hiccup in the moment between the two cannot make the step look unsent and send it again. The trade-off is a step whose outcome is genuinely unknown, when the worker stops responding mid-send: after 30 minutes with no answer the step is treated as a failed attempt, appears in **Needs attention**, and is retried like any other failure. + + + + ## Link tracking and UTM parameters With **Link tracking** on, every link in the email body becomes its own tracked link, so Warmbly records which link was clicked, not only that one was. Open a contact's **Activity** tab and a click reads **Clicked Pricing**, with the campaign, step and sending mailbox on the row and, expanded, the link's text, its full URL, every UTM parameter it carried, the browser, and whether a person or a scanner followed it. The campaign's live feed names the link the same way, and the `campaign.email_clicked` webhook carries the URL and the link text. @@ -273,6 +359,10 @@ With **Link tracking** on, every link in the email body becomes its own tracked A link that already carries one of these keeps the hand-written value; only the missing parameters are added. Existing query strings and fragments are left exactly as written, and the tags go on the real destination, so they survive the tracking redirect. Anchors, `mailto:` and `tel:` links, and anything that is not `http(s)` are never touched. UTM tagging works with link tracking off too: the tags are added and the link is otherwise left as written. Bare URLs in the plain-text part are tagged as well, numbered `link_1`, `link_2` since they have no text. Change the source, medium and campaign values under **Settings** > **Deliverability**; leave a field empty to use the default. + + + + ## Scheduling Campaigns send only inside the **weekly sending windows** you define, in the campaign's timezone. Each day is independent, with different hours or several windows per day, set from presets like `Mon-Fri 9-5` or by dragging on the grid (which is Monday-first). @@ -305,6 +395,10 @@ Follow-up spacing is separate and unaffected: that is each step's own wait, set Optional **campaign dates** bound when it may send. Leave both blank to run open-ended. Picking today as the start date means "start now", and clearing a start date on a running campaign removes the wait entirely. Schedule changes on an active campaign take effect immediately: the next send is recomputed on save, so a campaign waiting on a future start date starts sending as soon as you clear or shorten it. Past the end date nothing goes out at all, including a follow-up that came due before it: the campaign finishes instead, and its activity log says it reached its end date. + + + + ## Start and stop The play and pause buttons work from the list row or the detail view. Starting moves the campaign to **active** and begins scheduling inside your windows and limits; pausing stops new scheduling immediately and resumes from where it left off. @@ -315,6 +409,10 @@ An auto-pause is never silent. It writes a red entry to the campaign's activity A finished campaign starts again on its own when a lead is added to it, whether by a linked segment, the API, a form, an integration or an automation, through the same launch checks as pressing play. If a check refuses the restart (the list is a bounce risk, the plan cannot send, too many campaigns are active), the activity log says so and the new leads wait until you fix the cause and press play. Pressing play also works after extending or clearing the end date. If there is genuinely nothing left to send, pressing play turns on **Keep running for new leads** and the campaign stays active, **waiting for leads**, so it never dead-ends on a campaign that only ran out of people to write to; the activity log notes the switch. + + + + ## Duplicate and delete Every campaign has a **⋯** menu, on its row in the list and in the header of its detail view, with **Edit**, **Duplicate**, **Start** or **Pause**, and **Delete**. Both actions need the **Manage campaigns** permission; a member without it is told which permission is missing instead of the action failing later. @@ -323,6 +421,10 @@ Every campaign has a **⋯** menu, on its row in the list and in the header of i **Delete** asks for confirmation that spells out what goes: the campaign, its steps, lead progress and activity. Contacts and emails already sent stay. A running campaign can be deleted directly; the confirmation says so, and sending stops immediately because every task waiting for the campaign is cancelled together with it. The same action is available at the bottom of the campaign's **Settings**. Deletion cannot be undone. + + + + ## Auto-pause guardrails **Preferences** > **Opt-out line** chooses what this campaign appends after the signature: the workspace default from **Settings > Sending**, a reply-to-opt-out sentence, an unsubscribe link, or nothing. **Unsubscribe header** attaches the RFC 8058 headers alongside. See [unsubscribe and suppression](/guides/unsubscribe/). @@ -344,12 +446,20 @@ Setting a rate to `0` turns that rule off. The check runs every 15 minutes, and A paused campaign shows the reason and the numbers behind it on the **Auto-pause** section, and everyone on the team who can see campaigns gets a notification. Starting the campaign again clears the marker, but the next check re-evaluates from scratch, so fix the underlying problem first. + + + + ## Live activity The detail view streams sends, opens, clicks, replies, bounces, and skips as they happen, for every teammate at once. The **Sending** card appears only while an email is actually being handed to a worker, and always names the contact; between sends the panel shows the campaign's progress and the log, not a send in flight. A **Needs attention** panel above it surfaces failures such as a step that could not be scheduled, a send the worker could not complete, or a mailbox whose sending worker is offline; these retry automatically and stay visible so nothing fails silently. If scheduling stalls on a transient infrastructure hiccup, Warmbly re-seeds within a few minutes. Pausing and resuming forces a fresh start. + + + + ## Content checks Warmbly scores each step's copy out of 100 for the signals spam filters weight: trigger wording, stacked punctuation, ALL-CAPS subjects, link and image counts, image-only bodies, empty or oversized bodies, and attachments. @@ -397,12 +507,20 @@ The AI score is deliberately not sampled, so re-checking copy you did not touch When the draft has moved on from the analysis on screen, the panel says so and highlights the button, rather than presenting stale findings as current. + + + + ## Testing placement A content score reads the copy; a placement test sends it. Test any email step from **Placement tests** by choosing the campaign and the step: the copy is rendered the way this campaign sends it (merge fields for its first lead, its opt-out line, its tracking and attachments) and sent from one of its mailboxes to seed inboxes at the major providers, which report whether it reached the inbox, a Gmail tab or spam, or never arrived. Each copy is a send from that mailbox's daily limit. A **placement monitor** repeats that on a schedule while the campaign runs, every `7` days by default, rotating through the campaign's mailboxes. When a run lands below its alert threshold (`70%` inbox by default), everyone who can view campaigns is notified, and the monitor can pause the campaign too, as an auto-pause you restart yourself. See [inbox placement tests](/guides/placement-tests/). + + + + ## Easing out of warmup A diagnostic-history mailbox starts cold sending at a conservative ceiling of five/day. Synthetic traffic and elapsed age do not show that real recipients welcome outreach. @@ -416,6 +534,10 @@ A mailbox that never used warmup is not gated. This exists to smooth the warmup- + + + + ## The launch check on your list Starting a campaign measures its recipient list first and refuses to launch one that is known to be largely undeliverable. Mailbox providers put a sender under review at around a 5% bounce rate, so the number worth seeing is the one you can see beforehand. @@ -430,6 +552,10 @@ A refused launch shows why and offers **Launch anyway**, for a list you verified Above `4%` projected bounce the launch is refused with the number and what to do about it. Between `2%` and `4%` it launches with a warning. The same projection appears in the launch dialog before you click, so a refusal is never a surprise. + + + + ## Safety posture Defaults are deliberately conservative, aiming at a low complaint rate rather than maximum throughput: @@ -446,6 +572,8 @@ Choose configured caps using provider policy, recipient permission and observed When enabled, Warmbly limits a campaign mailbox to at most five diagnostic warmup messages per day, so campaign traffic stays primary. Diagnostic participation is not a proven reputation benefit and cannot compensate for a repetitive template sent to a poorly targeted list: personalize, test meaningful A/B variants, and honor suppression and unsubscribe signals. + + ## Related guides diff --git a/docs/content/docs/guides/collaboration.mdx b/docs/content/docs/guides/collaboration.mdx index a195493b9..cd36e01e5 100644 --- a/docs/content/docs/guides/collaboration.mdx +++ b/docs/content/docs/guides/collaboration.mdx @@ -3,12 +3,34 @@ title: Live collaboration description: See your team working in real time across the whole dashboard. --- +The dashboard is live: sends, opens, replies, new mail and other changes appear for everyone the moment they happen, with no refresh. You can also see who is online and what each teammate is working on. + +## Good to know + +- The header shows everyone with the dashboard open. Click a teammate to jump to their page. +- Anywhere a record opens, you see when a teammate has it open too. +- Press `/` on a shared page to chat from your cursor. +- Live events respect permissions: a member only receives what they are allowed to see. +- An admin can hide who is online, or what each person is doing, under **Settings > Workspace > Team presence**. + + + +## How the dashboard stays live + The dashboard is live by default: sends, opens, clicks, replies, new mail, contact changes, automation runs, meetings, and audit entries appear the moment they happen, for everyone, without a refresh. A presence layer on top shows who is online and what they are working on. + + + + ## Online teammates The header shows an avatar stack of everyone with the dashboard open. Hover for each person's name and where they are ("in unibox", "editing"). Click a teammate to jump straight to their page, which is the fastest way to join someone. + + + + ## Who is already on this? Anywhere a record opens, Warmbly shows when a teammate has it too: @@ -22,6 +44,10 @@ Anywhere a record opens, Warmbly shows when a teammate has it too: Indicators are workspace-scoped. Teammates see only your name, avatar, current page, and open record. + + + + ## What updates live - Campaign sends update progress, the leads list, and analytics for every viewer as the worker sends. @@ -37,12 +63,20 @@ Indicators are workspace-scoped. Teammates see only your name, avatar, current p A member restricted to [selected resources](/guides/team-roles/#access-scope) receives only events about what they were granted: new mail in their mailboxes, and activity on their campaigns. They are not part of presence or live cursors, in either direction: they do not appear online, and they do not see who else is or what teammates are viewing. + + + + ## Connection status A dot in the header reports the live connection: green **Live** when it is healthy, amber **Slow connection** or red **Poor connection** when heartbeats are taking longer than they should, amber **Reconnecting...** while it retries, and red **Disconnected** when it is down. The reading comes from the measured round trip to the realtime service, so amber means your connection genuinely is slow rather than merely busy. Stored data still catches up while it is down. Warmbly refreshes counts and lists on a slower cycle, and on reconnect it refetches everything that could have drifted. The events themselves are never replayed, so live-only signals such as presence and cursors resume from wherever things stand rather than backfilling what you missed. On a self-hosted install a permanently disconnected dot usually means the realtime service is not running; see [troubleshooting](/development/troubleshooting/). + + + + ## Live cursors and chat On a shared page you see each other's cursors move, tagged with avatar and name, across the contacts table, deal board, unibox, settings, and everywhere else. On app-scrolling pages the cursor tracks content as either person scrolls. @@ -51,12 +85,20 @@ Press `/` to chat from your cursor: an input opens at your pointer and what you Chat respects record permissions, enforced by the realtime service like the rest of the stream. + + + + ## Shared builder canvases The [automation builder](/guides/automations/) and [sequence](/guides/sequences/) editor are shared canvases. You see teammates' cursors and what each person has selected, outlined in their cursor color with their name, so "the step I have selected" is visible rather than described. Cursor chat works here too. Both lay themselves out from the flow's structure, so everyone sees the same picture without arranging anything. + + + + ## Presence privacy An admin controls this under **Settings > Workspace > Team presence** with two independent toggles: @@ -66,6 +108,12 @@ An admin controls this under **Settings > Workspace > Team presence** with two i The realtime service enforces both, so a hidden signal is **never sent**, not merely hidden in the interface. Changes apply immediately with no reload, and editing them needs the Manage settings permission. With "show activity" off, cursors and selections are not shared either. + + + + ## Developers The same stream, including presence events, is available over a WebSocket. See the [Realtime WebSocket](/api/realtime/) reference. + + diff --git a/docs/content/docs/guides/contacts-crm.mdx b/docs/content/docs/guides/contacts-crm.mdx index caf1c8706..7ca9a41c7 100644 --- a/docs/content/docs/guides/contacts-crm.mdx +++ b/docs/content/docs/guides/contacts-crm.mdx @@ -3,10 +3,66 @@ title: "Contacts & CRM" description: "Import contacts, custom fields, labels, deals, and pipelines." --- +Contacts are the people you reach out to. The CRM tracks what happens after they reply. + +![The contacts list with company, subscription status and campaign membership](/guides/contacts.webp) + +## Import contacts + + + + + +Start a file import from **Contacts**: a CSV, TSV or XLSX file of up to 50 MB and 50,000 rows. + + + + + +**Upload** the file. Nothing is written to your contacts yet. + + + + + +**Map columns**, with at least one column mapped to **Email**. + + + + + +On **Review**, check the counts and choose **Leave as is** or **Update details** for contacts you already have. + + + + + +**Import**. It runs on the server, so you can close the window while it finishes. + + + + + +## Good to know + +- Duplicates match on lowercased email across the workspace, so one address is one contact. +- A blank cell never erases a value you already have, so re-importing a partial export is safe. +- Custom fields feed personalization, such as `{{.industry}}` in an email. +- Unsubscribed contacts are skipped by every campaign automatically. +- **Save as segment** turns the filters on the list into a reusable [segment](/guides/segments/). + + + +## How the list remembers your view + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. Contacts are the people you reach out to; the CRM tracks what happens after they reply. + + + + ## Importing Two routes share the same column-mapping screen: a file upload and an on-demand Google Sheets sync. @@ -76,6 +132,10 @@ The Review step and the result also report what the addresses themselves look li This is not address verification. It reads the addresses; verification asks the receiving server whether they exist, and runs separately in the background, within a minute of the import. A file that already carries verification results from another service (a `status` or `ZeroBounce Status` column, say) is recognised during mapping and offered as **Verification status**; those verdicts are kept and the background check skips them. See [address verification](/guides/deliverability/#address-verification). + + + + ## Custom fields For anything beyond identity: industry, plan tier, account owner. Create them by mapping a column to a new custom field during import, or on a contact's Details tab. A later import can map into the same field by picking it from the mapping menu (see [Mapping into your custom fields](#mapping-into-your-custom-fields)). @@ -102,24 +162,40 @@ Both forms work everywhere, including inside `{{if}}` conditions and helper func See [Personalization & expressions](/guides/expressions/) for the full templating language. + + + + ## Filtering the list A filter bar sits above the contact list. **Label**, **Segment**, **Status** and **Campaign** are always there; open one, tick values, and the list updates immediately with the matching count next to the bar. **Add filter** adds a custom-field condition (field, contains/is/starts with/ends with, value), a date-added or last-updated range, a number-of-campaigns range, the address verification verdict, the [email provider](#email-provider) and, on a campaign's Leads tab, lead status and engagement. Each active filter is a pill you can reopen to change or remove with its cross; **Clear** drops them all, and **Save as segment** turns the current set into a [segment](/guides/segments/). Free-text search, sort and the column chooser stay in the toolbar. Free-text search matches first name, last name, email, company and phone. Every word you type has to match one of those, so `Test Demo` finds the contact whose first name is Test and last name is Demo, and `Demo Acme` finds everyone named Demo at Acme. Words can be in any order, and only the first six count. + + + + ## Choosing columns **Columns** in the toolbar opens the column chooser. A column of optional details (Phone, Company, a custom field) that nobody in the list you are looking at has filled in steps aside rather than showing a column of dashes, and comes back as soon as one contact has a value; the chooser marks it **empty here**. Tick a column to show it, untick it to hide it, and drag the grip beside a shown column to change the order. Name is always the first column. Under **Custom fields** every custom field in the workspace is offered as a column, so a list imported from Apollo or Clay can show the fields you triage on (industry, title, employee count) without opening each contact. The layout is yours: it is saved to your account for this workspace, so it follows you to another browser, and a teammate looking at the same list keeps their own columns. The campaign Leads tab has its own layout, separate from the contacts page. **Reset to default** at the bottom of the chooser puts the standard columns back. + + + + ## Sorting Click a column header to sort by it, and click it again to flip the direction. Text columns (name, company, phone, custom fields) start ascending; dates and counts start with the newest or largest first. The **Sort** menu in the toolbar reaches the same orderings, including columns that are hidden or that a phone screen has no room for, email provider, and every custom field in the workspace. A sort on a custom field orders the values as text. Contacts that do not have the field, or have it blank, are grouped at the end when ascending and at the start when descending. The sort is saved with your layout, so the list opens the way you left it. + + + + ## Email provider Each contact's avatar carries a small badge with the logo of whoever hosts their inbox: Google Workspace, Gmail, Microsoft 365, Outlook.com, Yahoo Mail, iCloud Mail, Zoho Mail and the other hosts Warmbly recognises. Hover it for the provider's name. It works for company domains too: `dana@acme.com` reads as Google Workspace when Acme's mail is hosted by Google. Warmbly reads it from the domain's mail records in the background, usually within a minute of a contact being added, and fills the list in live. No badge means the check has not reached the contact yet, or the domain has no mail server Warmbly recognises. @@ -128,6 +204,10 @@ A contact on a company domain shows that company's logo in place of their initia It shows on the contacts page and on a campaign's Leads tab alike. You can sort by it from **Sort**, filter with **Add filter** > **Email provider** (including **Unknown or not checked yet**), select every match and act on them in bulk (add them to a campaign, label them, export them), or save the filter as a segment. Campaign [ESP matching](/guides/campaigns/) uses the same data to pair each lead with a same-provider mailbox. + + + + ## Selecting rows Tick a row's checkbox to select it, or the one in the table header to select every row loaded so far. Contact lists load in pages as you scroll, so the header checkbox on its own only ever covers what is on screen. @@ -140,18 +220,30 @@ Every bulk action reads the selection: **Edit**, **Segment**, **Remove from segm **Select all matching** is in the **From contacts** picker too, on a campaign's Leads tab and a segment page, so a whole search can be added as leads or members in one step. + + + + ## Editing one contact Clicking a row opens the contact's panel. Its **Details** tab edits the fields the contact is made of: name, email address, company, phone, subscription, campaigns, labels and custom fields. Nothing is sent until **Save**, **Discard** puts the panel back to the stored values, and closing it with unsaved edits asks first. The email address can be changed here, which is the right move when someone's address was mistyped on import or they moved to a new domain. It is stored lowercased and stripped of any display name, and it has to be free: an address another contact already holds is refused, because merging two people's campaign history is not something the edit could undo. A new address also clears the contact's [verification](/guides/deliverability/#address-verification) verdict and everything the platform had observed about the old mailbox, so the background check starts the new address from scratch on its next pass, and the recipient provider Warmbly matches senders against is worked out again from the new domain. Emails already sent went to the old address and stay in the timeline as they happened. + + + + ## Editing many contacts at once Tick rows in any contact list, a segment's members or a campaign's leads, and the selection bar's **Edit** opens a bulk panel. It can add or remove campaigns, add or remove labels, force a subscription state, and queue custom-field operations (add, edit, delete, rename) that run on every selected contact (the key box suggests your existing fields as you type), including a **Select all matching** selection that reaches past the loaded pages. Nothing is applied while you build it up. The header names the list the selection came from, and a **Will apply** strip above the buttons lists every queued change as a chip you can take back one at a time before pressing Apply. A field operation missing its key, or its value where one is needed, is marked and skipped rather than sent half-finished. + + + + ## Labels Colored labels that group and filter contacts (`Warm lead`, `Conference 2026`, `Enterprise`). The same list labels [inbox conversations](/guides/unibox/#labels) and is what [forms](/guides/forms/) file their submissions under. The same picker appears in bulk edit, a contact's Details tab, the new-contact dialog, the filters, and the import and sync wizards. @@ -164,6 +256,10 @@ Labels also drive automation: a sequence can run **Add label** or **Remove label To turn labels and activity into a reusable audience, build a [segment](/guides/segments/): a saved set of conditions over contacts (labels, fields, campaign activity, engagement) that you can browse and add to a campaign in one step. + + + + ## Where a contact came from Every contact records its first-touch source: how it entered the workspace, with a detail you would recognise, and when it was first seen. It is shown on the contact's **Overview** tab and as the first event of its activity timeline, and it never changes afterwards, however the contact is edited or re-imported. @@ -184,6 +280,10 @@ Only new contacts get a source. An import or API call that matches an existing c A new contact is also an event. `contact.created` goes to your [webhooks](/guides/webhooks/) and can start an [automation](/guides/automations/#triggers) with the contact's fields and source, except for bulk arrivals, which stay silent: file imports, sheet syncs, and a single API request adding more than 100 contacts. + + + + ## Activity timeline The **Activity** tab of a contact is one workspace-wide feed, newest first, of everything Warmbly knows about them. Every member with permission to view contacts sees the same timeline, regardless of who created the contact or its campaigns. It includes every campaign email sent, opened, clicked, replied to or bounced (with the campaign, step, subject and sending mailbox), replies with their classified intent, deliverability and suppression events, notes, meetings, and the contact's lifecycle: when it was created and how, and each time it joined or left a campaign or gained or lost a label. @@ -194,10 +294,16 @@ Filter chips narrow the feed (**Emails**, **Replies**, **Deliv.**, **Notes**, ** At the top of the tab sits the campaign panel: for each campaign the contact is in, its flow with this contact's progress, the lead status, and what the scheduler will do next. See [Campaigns](/guides/campaigns/) for how the next action is worked out and what its states mean. + + + + ## Deals and pipelines A **pipeline** is a named, ordered sequence of **stages** a deal advances through. On the Pipelines page you create pipelines (new ones start with `Open`, `Qualified`, `Won`), then add, rename, recolor, and delete stages, each showing its deal count. Stage order is the left-to-right column order on the board. +![Deals across every pipeline, with open and won value at the top](/guides/deals.webp) + | View | Behavior | |------|----------| | **Table** (default) | Every deal across all pipelines, server-paginated. Totals like open count and pipeline value are real sums over the whole result, not just loaded rows | @@ -229,6 +335,10 @@ Each mirrored record links to its HubSpot page. Each mirrored record links to its Pipedrive page. + + + + ## Tasks **CRM > Tasks** is the follow-up list for the whole workspace: everything anyone has to do about a contact or a deal, in one place. A task carries a title, a description, a type (`Call`, `Email`, `Meeting` by default, and any type you add), a due date, a priority of `low`, `medium`, `high` or `urgent`, a status of `pending`, `in progress`, `completed` or `cancelled`, and an assignee who can be a person, a team, or nobody. @@ -263,6 +373,10 @@ Automated replies are off deliberately. A vacation notice is not follow-up work, The classification does more than decide the task: an automated reply never counts as a reply for **stop on reply** or for a **replied** branch in a sequence, and its notification goes to the **Out-of-office detected** preference rather than **New reply**, so muting one does not mute the other. + + + + ## Suppression A suppressed contact receives no further mail. Unsubscribed contacts are skipped by every campaign automatically; toggle subscription on the Details tab, and filter the list by Subscribed or Unsubscribed. @@ -271,6 +385,8 @@ Suppression also happens automatically when a recipient unsubscribes or replies This is the safe default response to a bad signal: stop sending rather than keep collecting bounces and complaints. See [unsubscribe and suppression](/guides/unsubscribe/) for the full picture and [Deliverability](/guides/deliverability/) for the signals behind it. + + ## Where to go next diff --git a/docs/content/docs/guides/deliverability.mdx b/docs/content/docs/guides/deliverability.mdx index a82d8bb60..b25f9f6c6 100644 --- a/docs/content/docs/guides/deliverability.mdx +++ b/docs/content/docs/guides/deliverability.mdx @@ -3,12 +3,62 @@ title: "Deliverability" description: "Bounce, complaint, and spam-placement health, with the thresholds Warmbly acts on." --- +Deliverability is whether your mail reaches the inbox instead of bouncing, getting marked as spam, or landing in junk. Warmbly tracks it per mailbox and per campaign, and acts automatically when a mailbox looks risky. + +![The deliverability dashboard: score, bounce and complaint rates over time, and where warmup mail lands](/guides/deliverability.webp) + +## Read the dashboard + + + + + +Pick a window with the `7d`, `30d` or `90d` range tabs. + + + + + +Start with the headline rates: bounce at `5%` and complaints at `0.1%` are the highlighted danger lines. + + + + + +If a rate looks off, switch the chart to that metric and find the day it spiked. + + + + + +Read the at-risk lists to see which mailboxes and campaigns Warmbly is already acting on. + + + + + +## Good to know + +- Each mailbox sits in a health band (Healthy, Watch, Throttled, Quarantine or Blocked) that controls how Warmbly treats it. +- Spam placement never goes past Throttled. It slows the mailbox down and lifts on its own. +- A complaint rate of `0.10%` or a bounce rate of `5%` quarantines a mailbox: out of the shared paid warmup pool for `7 days`, with cold sending paused. +- Warmbly suppresses a recipient automatically on a bounce, a spam complaint, or an unsubscribe, and no campaign emails them again. +- A domain that stays Failing on SPF or DMARC past the grace period (`72 hours` by default) stops cold campaign and warmup sending. Your own replies always go out. + + + +## What deliverability covers + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. Deliverability is whether your mail reaches the inbox instead of bouncing, getting marked as spam, or landing in junk. Warmbly tracks it per mailbox and per campaign, and acts automatically when a mailbox looks risky. These defaults are deliberately stricter than the point where Google or Microsoft start penalizing senders, so action happens before provider enforcement hits your reputation. + + + + ## The dashboard Scope everything to `7d`, `30d`, or `90d` with the range tabs. It updates live, so no refreshing. @@ -45,6 +95,10 @@ Metric metadata specifies the population, numerator, denominator, missingness, u Google, Microsoft, and Yahoo family totals are not interchangeable with their consumer and business services. Inspect the host-level breakdown when Gmail versus Google Workspace or Outlook.com versus Microsoft 365 matters. Hosts that cannot be distinguished remain unknown. Pool placement, organic recipient activity, opens, bounces, and complaints stay separate. A **rescue request** is an action attempt, not confirmed movement or proof that reputation recovered. + + + + ## Health bands Each mailbox sits in a band, shown as a colored chip. The band controls how Warmbly treats it. @@ -78,6 +132,10 @@ Warmbly never acts on the composite score alone. Complaint rate, spam placement, Time elapsing is not enough. A blocked mailbox must requalify: SPF, DKIM, and DMARC still healthy; no recent complaints or hard-bounce spikes; no recent tampering with received warmup mail; spam placement back under `10%` on a fresh probation sample. Re-entry then starts low, around `5-10/day` of warmup. + + + + ## Domain authentication SPF, DKIM, and DMARC requirements depend on the provider and sending context. Warmbly checks DNS records about once a day and on demand from the mailbox. This is record discovery, not actual-route alignment. @@ -116,6 +174,10 @@ Re-check is also worth pressing after reconnecting a mailbox that was disconnect If a campaign has already been paused because every one of its mailboxes was gated, the activity log says so, and you restart the campaign yourself once the domain reads Passing. + + + + ## Built-in protections **An opt-out in every email.** Each campaign email ends with a way to stop hearing from you: by default a plain "just reply and let me know" sentence, whose replies are detected and honoured automatically, or an unsubscribe link. Eligible marketing/bulk messages can attach one-click unsubscribe headers. RFC 8058 requires an HTTPS URI, `List-Unsubscribe-Post: List-Unsubscribe=One-Click`, and a valid DKIM signature covering both headers. The endpoint must process the POST without cookies, authorization context, or a redirect. Header presence and claimed signing coverage alone do not establish compliance; the delivered signature and endpoint must be checked. This requirement does not turn an ordinary personal reply into bulk marketing mail. See [unsubscribe and suppression](/guides/unsubscribe/). @@ -136,6 +198,10 @@ Complaint reports are read from IMAP and Gmail mailboxes. Microsoft Graph return **Domain authentication refusals** are separated from ordinary failures. When a receiving server refuses a message because your sending domain failed its checks (Microsoft's `5.7.515`, Gmail's `5.7.26`), that is not a bad recipient and not an outage: every send from that domain fails the same way until its DNS is fixed. Warmbly says so in the campaign's activity feed rather than blaming the address, does not suppress the recipient, and brings that domain's authentication check forward so the [domain authentication](/guides/deliverability/#domain-authentication) state reflects it. + + + + ## Address verification Every address gets one of four verdicts. **Deliverable** and **unverified** are always sent to. **Undeliverable** is never sent to. **Risky** (a catch-all domain, a shared inbox such as `info@`) is sent to only when the campaign's **send to risky addresses** setting allows it. @@ -173,6 +239,10 @@ The Deliverability card always says who produced the verdict and when: **Verifie A campaign never quietly finishes because of verification. When every remaining lead has been refused, the campaign shows **needs verification** and pauses, with two buttons on it: **Re-verify leads** checks them again and resumes sending automatically as soon as any pass, and **Send anyway** marks them deliverable and resumes at once. The [launch check](/guides/campaigns/#adding-leads) likewise offers **Launch anyway** when a list is refused for its projected bounce rate, for a list verified elsewhere. + + + + ## Workspace sending posture Individual protections each watch one thing: a rate limit watches one user, warmup health watches one mailbox, verification watches one address. A workspace that is slightly wrong on several of those at once sits under every individual threshold and is never noticed. The workspace posture is the view that sees all of them together. @@ -204,6 +274,10 @@ No posture is permanent, and there are three ways out of one. If your workspace is restricted or suspended and you believe it is wrong, reply to the notice or contact your platform operator and ask for a review. The banner's reason is the same sentence they see. + + + + ## How to read it 1. Start with the headline numbers. Bounce at `5%` and complaints at `0.1%` are the highlighted danger lines. @@ -214,6 +288,8 @@ If your workspace is restricted or suspended and you believe it is wrong, reply The habit that matters: keep complaint rate and spam placement low. A clean reputation at modest volume beats high volume with rising complaints every time. + + ## Related guides diff --git a/docs/content/docs/guides/expressions.mdx b/docs/content/docs/guides/expressions.mdx index 4e1d9c20f..8431c2c1d 100644 --- a/docs/content/docs/guides/expressions.mdx +++ b/docs/content/docs/guides/expressions.mdx @@ -3,8 +3,50 @@ title: "Personalization & Expressions" description: "Merge fields, conditionals, fallbacks, functions, spintax, and automation expressions: the templating language shared by campaign emails and automations." --- +Personalization lets you write one email that comes out tailored for every contact, using merge fields like `{{.FirstName}}`. The same language also powers automation conditions and action text, and needs no programming knowledge. + +## Personalize an email + + + + + +In a campaign step's subject or body, type a merge field such as `{{.FirstName}}`. + + + + + +Give fields that may be empty a fallback: `{{.FirstName | default "there"}}`. + + + + + +Open the Preview to check it renders as expected, with no leftover `{{...}}` tokens. + + + + + +## Good to know + +- Field names are case-sensitive: `{{.FirstName}}` works, `{{.firstname}}` does not. +- Unknown variables render empty rather than erroring. +- Every `{{if}}` must close with `{{end}}`. A broken one blocks launching the campaign. +- Spintax such as `{Hi|Hey|Hello}` varies wording in campaign subjects and bodies only. +- Automations use the trigger's lowercase keys, such as `{{.contact_email}}`, still with the leading dot. + + + +## What personalization is for + Write one email and have it come out personalized for every contact, using merge fields like `{{.FirstName}}` plus helpers for fallbacks, conditions, and math. No programming knowledge needed; match the patterns below. + + + + ## Quick reference | You want to | Write | @@ -17,6 +59,10 @@ Write one email and have it come out personalized for every contact, using merge | Randomize wording (campaigns only) | `{a\|b\|c}` | | Filter an automation by a number | `gtf .confidence 0.8` | + + + + ## Variables ### Contact fields (campaigns) @@ -96,6 +142,10 @@ New reply from {{.contact_email}} on campaign {{.campaign_id}} ({{.intent}}) Action text is a **full template, not token substitution**: conditionals, pipelines, helpers, `{{range}}`, and nested fields (`{{.lead.company}}`) all work, and values keep their type. Because numbers stay numbers, `{{if gt .confidence 0.8}}` works directly in a message body. A malformed template never blocks the action; it falls back to plain `{{.key}}` substitution. Unknown keys render empty. `{{range}}` loops over a field of the data (`{{range .lead.tags}}`), never a number or a function's result, and nests at most two deep; `{{template}}` and `{{block}}` are not supported, and output is capped at 1 MB. A template past any of these limits falls back the same way. + + + + ## Fallbacks Put the field first, then a pipe, then `default` and your backup in quotes: @@ -105,6 +155,10 @@ Hi {{.FirstName | default "there"}}, Hope things are going well at {{.Company | default "your company"}}. ``` + + + + ## Conditionals `{{if}}` includes text only when something is true, `{{else}}` handles the other case, and every `{{if}}` must close with `{{end}}`. @@ -116,6 +170,10 @@ Hope things are going well at {{.Company | default "your company"}}. `eq` is equals and `ne` is not-equals. Quote your text values. + + + + ## Functions The same helpers work in campaign emails, automation conditions, and action text. @@ -143,6 +201,10 @@ Comparing automation numbers? Native `gt` / `ge` works. Comparing a custom field {{if hasSuffix .Email ".edu"}}As someone in academia, {{end}}I thought this might help. ``` + + + + ## Spintax Alternatives in single braces separated by pipes, so a batch of sends does not look identical: @@ -155,6 +217,10 @@ Alternatives in single braces separated by pipes, so a batch of sends does not l Campaign subject and body only; it does not run in automations. Use **single** braces, not the double braces of merge fields. Only groups containing a pipe count, so a brace run without one (CSS or JSON in an HTML email) is left exactly as written. + + + + ## Advanced automation expressions For anything the visual picker cannot express. An expression passes when it renders to something truthy; it is **false** when empty, `false`, `0`, `no`, `off`, or ``, and a broken expression is false rather than silently passing. @@ -175,6 +241,10 @@ hasSuffix .contact_email "@acme.com" `and`, `or`, and `not` take whole tests as inputs, so wrap each in parentheses when mixing groups. + + + + ## Where each thing applies | Surface | Variables | Conditionals / functions | `default` | Spintax | @@ -183,6 +253,10 @@ hasSuffix .contact_email "@acme.com" | Automation condition | Trigger keys | Yes | Yes | No | | Automation action text | Trigger keys, with the dot | Yes | Yes | No | + + + + ## Gotchas - Standard fields are case-sensitive: `{{.FirstName}}`, not `{{.firstname}}`. @@ -192,8 +266,14 @@ hasSuffix .contact_email "@acme.com" - Unknown variables render empty rather than erroring. A leftover `{{...}}` usually means a typo or wrong capitalization. - In action text keep the leading dot. It is standard Go `text/template`, so a bare `{{contact_email}}` is invalid. + + + + ## Preview and validation **Campaigns**: the composer's live Preview renders against a sample contact exactly as the real send does (variables, conditionals, functions, then spintax). It flags errors like a broken `{{if}}`, which block launching, and unresolved tokens left after rendering. **Automations**: an advanced expression is validated on save, so a condition that does not parse cannot be saved. The **Reference** button lists every variable and function inline. + + diff --git a/docs/content/docs/guides/forms.mdx b/docs/content/docs/guides/forms.mdx index 96d78bfe7..6c31b8205 100644 --- a/docs/content/docs/guides/forms.mdx +++ b/docs/content/docs/guides/forms.mdx @@ -3,12 +3,60 @@ title: "Forms" description: "Build a hosted lead-capture form, style it to match your site, embed it anywhere, and turn every submission into a contact." --- +Forms turn website visitors into contacts. You build a form, publish it to a hosted page, and share the link or embed it on any website. + +## Publish a form + + + + + +Open **Forms** in the sidebar and choose **New form**. + + + + + +Add and arrange blocks in the builder. The canvas shows exactly what the hosted page renders. + + + + + +Style it on the **Design** tab, then pick labels or a campaign on the **Settings** tab. + + + + + +Press **Publish**, then copy a link or embed code from the **Share** tab. + + + + + +## Good to know + +- A submission with an email answer creates or updates a contact. Without one, it is stored but never touches the contact list. +- **Add to campaign** enrolls new contacts as leads, but a form never causes immediate mail. +- Every published form has spam protection with nothing to configure. +- Edits to a published form go live when you save. +- Viewing forms takes the same permission as viewing contacts; building and publishing take the manage-contacts permission. + + + +## What forms do and who can use them + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. Forms turn website visitors into contacts. You build a form in the dashboard, publish it to a hosted page, and either share the link or embed the form on any website. Every submission is stored, and when it carries an email address it creates or updates a contact, applies the labels you chose, and can drop it straight into a campaign. Open **Forms** in the sidebar. Viewing takes the same permission as viewing contacts; building and publishing take the manage-contacts permission. + + + + ## Building a form **New form** creates a draft seeded with first name, last name and email, and opens the builder. The canvas in the middle is a live preview: what you see is exactly what the hosted page renders. @@ -43,6 +91,10 @@ Every answer is kept verbatim on the submission. On top of that, each input fiel A form needs an email answer to create a contact. Forms without an email field still collect submissions; they just never touch the contact list. + + + + ## Design The **Design** tab styles the form while the canvas updates live. The defaults are a clean, neutral card that looks right on most sites; changing nothing is a fine choice. @@ -55,6 +107,10 @@ The **Design** tab styles the form while the canvas updates live. The defaults a - **Side panel.** On the Split layout, the panel takes a title and a paragraph of text on top of the cover image, rendered in white with a soft shadow so it stays readable on any photo. - **Branding and backgrounds.** Upload a logo (PNG or JPG, up to 1 MB), a cover image for the Split layout, and a background image behind the whole page (both up to 4 MB). The logo renders small, medium or large, and on the Card layout it can sit on the card or above it on the page background; check contrast if you move it out, since a dark page can swallow a dark logo. A background image can fill, fit or tile, and the **veil** slider fades the page color over it: raise it when text on a busy photo gets hard to read. + + + + ## Settings The **Settings** tab controls what a submission does: @@ -64,6 +120,10 @@ The **Settings** tab controls what a submission does: - **Add to campaign** enrolls new contacts as leads in the campaign you pick. Sending still follows the campaign's own schedule, limits and windows; a form never causes immediate mail. Picking a campaign turns on its **Keep running for new leads** setting, so it waits between submissions instead of finishing; a campaign that had already finished restarts when a lead arrives, through the usual launch checks, and a refused restart is noted in its activity log while the lead waits. - **Spam protection** and **allowed embed domains** are covered below. + + + + ## Publishing and sharing **Publish** puts the form online; **Unpublish** takes it offline again (the page starts answering not found). Archiving keeps the data but takes the page offline too. Edits to a published form go live when you save. @@ -110,6 +170,10 @@ A campaign that links to a form grows a **Forms** panel on its overview page, li Because each recipient's link is unique, those numbers are per contact rather than per click: the same person opening twice counts once, and every submission names the campaign that sent them. The form's own Analytics tab has the mirror image, a **Campaigns** breakdown of where its traffic came from, and every submission row shows its campaign. + + + + ## Submissions The **Submissions** tab is a table of everyone who engaged, newest activity first and live: a new submission appears for everyone with the form open, without a refresh. Alongside completed responses it lists people who opened a personalized link but have not finished, marked **In progress** with the page they reached, so a stalled lead is visible instead of invisible. Search and the status filter narrow the list; click a response for every answer; the trash button deletes it (the contact it created is kept), and selecting rows offers a bulk delete. @@ -120,6 +184,10 @@ A submission is also the **Form submitted** [automation trigger](/guides/automat Submissions also fire the `form.submitted` [webhook](/guides/webhooks/), carrying the answers and the contact id, so you can forward leads to any external system. + + + + ## Analytics The **Analytics** tab shows the funnel for the last 7, 30 or 90 days: @@ -131,6 +199,10 @@ The **Analytics** tab shows the funnel for the last 7, 30 or 90 days: Funnel events are kept for 180 days; the lifetime view and submission counters on the Forms list are kept forever. + + + + ## Spam protection Every published form has layered protection with nothing to configure: @@ -149,6 +221,12 @@ Three more controls are yours: Worth listing your domains even if you only embed on one site. A form left open to any origin can be framed invisibly under someone else's page and used to collect leads that look like yours, and the browser is the only thing that can prevent it. + + + + ## Data and portability Forms, their design, their logo and cover images, their submissions, personalized link tickets and funnel events all belong to the **Contacts** data group of a [workspace archive](/guides/workspace-export-import/). The public form id travels with them, so embed codes installed on your website keep working after a move to another instance (point your embeds at the new host), and link tickets travel verbatim, so personalized links already sitting in sent emails keep identifying their contacts. A form's campaign link is dropped on import when campaigns stay behind. + + diff --git a/docs/content/docs/guides/hubspot.mdx b/docs/content/docs/guides/hubspot.mdx index b60d1f4be..131c451d8 100644 --- a/docs/content/docs/guides/hubspot.mdx +++ b/docs/content/docs/guides/hubspot.mdx @@ -3,10 +3,58 @@ title: HubSpot description: "Run your workspace's CRM on HubSpot: deals, tasks and notes written through, outreach logged on the timeline, and campaigns that stop when HubSpot says a contact has moved on." --- +HubSpot mode makes HubSpot your workspace's CRM. The deals, pipelines, tasks and notes you see in Warmbly are HubSpot's records, and your outreach is logged on each contact's HubSpot timeline. + +## Switch to HubSpot + + + + + +Open **Integrations > HubSpot** and select **Connect**. + + + + + +Sign in to HubSpot, pick the account, and approve the permissions. + + + + + +Go through the setup wizard. Every question has a default that works, so you can finish without changing anything. + + + + + +Finish the wizard. The dashboard fills in as the first pull runs, usually within a minute or two. + + + + + +## Good to know + +- Every change you make in Warmbly is written to HubSpot first. If HubSpot refuses it, nothing is saved. +- A deal opened in HubSpot, a lifecycle stage you choose or an email opt-out holds the contact's campaigns. +- Pipelines and stages are managed in HubSpot only. +- It works with free HubSpot. +- Switching back or disconnecting deletes nothing on either side. + + + +## How HubSpot mode works + A workspace can use Warmbly's own CRM or HubSpot. In **HubSpot mode**, HubSpot is the system of record. The deals, pipelines, tasks and notes you see in Warmbly are HubSpot's records, mirrored into Warmbly so lists, search and the board stay fast, and every change you make in Warmbly goes to HubSpot first. Your outreach is logged on each contact's HubSpot timeline, and HubSpot can stop a campaign when a contact becomes a deal or a customer. The basic HubSpot connection (a contact upsert from an [automation](/guides/automations/#actions) or **Push to CRM**) works without HubSpot mode. This page covers the full mode. + + + + ## What HubSpot mode does - **Deals, pipelines, tasks and notes are HubSpot's.** A deal you create, move or edit in Warmbly is written to HubSpot before it is saved here. If HubSpot refuses it (a required property, a missing permission), you see HubSpot's reason and nothing is saved. @@ -15,6 +63,10 @@ The basic HubSpot connection (a contact upsert from an [automation](/guides/auto - **HubSpot can stop outreach.** A deal opened in HubSpot, a lifecycle stage you choose or an email opt-out holds the contact's campaigns. - **The contact panel shows HubSpot's side.** In the contact drawer and the inbox, a contact shows its HubSpot owner, lifecycle stage, lead status, company and any properties you choose, and you can edit the owner, lifecycle stage and lead status in place. + + + + ## Connect and set up 1. Open **Integrations > HubSpot** and select **Connect**. Sign in to HubSpot, pick the account, and approve the permissions. Connecting takes someone who can install apps in that HubSpot account. @@ -27,6 +79,10 @@ After setup the same page holds the settings and the [sync health](#sync-health- Earlier HubSpot connections only asked for contact access. HubSpot mode also needs companies, deals, owners and the contact schema, so the page asks you to reconnect before you can switch, and the switch is refused with `crm_reauth_required` until you do. + + + + ## What syncs | Record | Direction | How | @@ -64,6 +120,10 @@ The Tasks page shows the HubSpot tasks owned by HubSpot users who are matched to Creating, editing or deleting a task type is refused with `crm_managed_externally`, because HubSpot's task types are fixed. Tasks Warmbly opens by itself, such as [follow-up tasks from replies](/guides/contacts-crm/#follow-up-tasks-from-replies) and an automation's **Create a task**, are written to HubSpot as well. + + + + ## Contacts and field mapping A Warmbly contact is linked to a HubSpot contact by email address. Linking happens when Warmbly emails the contact, when you open it, when HubSpot changes a contact whose address Warmbly has, and after an [import from a HubSpot list](#importing-from-hubspot-lists). @@ -82,6 +142,10 @@ The defaults map first name, last name, company and phone to `firstname`, `lastn You can also pick up to 40 extra HubSpot properties to show on the contact panel. + + + + ## Activity logged to HubSpot | In Warmbly | In HubSpot | Default | @@ -98,6 +162,10 @@ Each email activity is associated with the contact, its company and its open dea Opens and clicks are off by default because mail privacy proxies and security scanners open and click on their own, and HubSpot timelines fill with noise. Turned on, they only update the two properties, at most once an hour per contact, and never create a contact in HubSpot. + + + + ## The Warmbly property group Warmbly creates a **Warmbly** group of contact properties in HubSpot when you switch, and keeps it up to date (on by default; turning it off stops every property write, including opens and clicks). Properties you or your HubSpot admins already changed are left as they are. @@ -114,6 +182,10 @@ Warmbly creates a **Warmbly** group of contact properties in HubSpot when you sw | `warmbly_unsubscribed` | Set when the contact unsubscribes from Warmbly outreach | | `warmbly_link` | A link that opens the contact in Warmbly | + + + + ## Owners HubSpot's owners (its users) are matched to workspace members by email address. Where the addresses differ, pick the member by hand on the HubSpot page; a match you choose is kept and never replaced by the email match. Matching decides: @@ -123,6 +195,10 @@ HubSpot's owners (its users) are matched to workspace members by email address. - which HubSpot owner a deal or task gets when you assign it to a member in Warmbly - who the [Warmbly card](#working-from-inside-hubspot) acts as, and so what a HubSpot user can do from it + + + + ## Positive replies When a reply is classified as interested, Warmbly can: @@ -133,6 +209,10 @@ When a reply is classified as interested, Warmbly can: The follow-up task comes from your existing [reply follow-up setting](/guides/contacts-crm/#follow-up-tasks-from-replies) and is written to HubSpot like any other task. + + + + ## Exit rules Exit rules hold a contact in every campaign it is in when HubSpot says it has moved on, so nobody gets a follow-up after becoming a deal or a customer. A held lead stays held until someone resumes it; the hold shows the reason, such as "A deal was opened in HubSpot". @@ -154,6 +234,10 @@ Guards decide who an [import from a HubSpot list](#importing-from-hubspot-lists) | Skip contacts with an open deal | On | | Skip contacts owned by someone outside the workspace | Off | + + + + ## Working from inside HubSpot Two things put Warmbly inside HubSpot itself, so a rep never has to switch tabs to act on a contact: @@ -173,6 +257,10 @@ A HubSpot user with no matched member, or whose member is still invited, is bann The card and the workflow action need the HubSpot account to be in HubSpot mode in exactly one Warmbly workspace. When more than one workspace has it connected in HubSpot mode, both refuse until only one does. + + + + ## Importing from HubSpot lists On the HubSpot page, pick one of your HubSpot lists. The preview counts who would come in and who is skipped, by reason: opted out, lifecycle stage, open deal, owned by someone outside the workspace, no email address, and duplicate address. Continue, and the list becomes an ordinary contact import, finished in the usual [import review](/guides/contacts-crm/#importing) where you pick the campaign, labels and duplicate handling. @@ -181,6 +269,10 @@ One import takes up to 25,000 contacts; a larger list is cut off at that number List import needs HubSpot's list permission, which is optional. A connection without it is asked to reconnect and allow list access. + + + + ## Copying your Warmbly CRM into HubSpot When a workspace that used Warmbly's CRM switches, its own deals, tasks and notes are not in HubSpot yet. The HubSpot page counts them and copies the kinds you pick, once: @@ -190,6 +282,10 @@ When a workspace that used Warmbly's CRM switches, its own deals, tasks and note A record already in HubSpot is never copied twice, so running it again only picks up what was missed. A Warmbly deal, task or note you edit after switching is also created in HubSpot at that moment. + + + + ## Sync health and fixing errors The HubSpot page shows what is waiting, what failed in the last runs, how many records are mirrored, and when each pull (owners, pipelines, deals, tasks, contacts) last ran with any error it hit. **Sync now** starts a full pull; a second one within 30 seconds is refused with `crm_sync_running`. @@ -206,10 +302,18 @@ Changes you make in the dashboard go to HubSpot while you wait. Activity logging Warmbly keeps its calls under HubSpot's limit for each HubSpot account (about 90 requests every 10 seconds, shared across every Warmbly process), and waits and retries when HubSpot asks it to slow down. + + + + ## Switching back and disconnecting Choosing **Warmbly** as the CRM again, or disconnecting HubSpot, stops the sync and the activity logging. Nothing is deleted on either side: the deals, pipelines, tasks and notes that were mirrored stay in Warmbly as ordinary records you can edit, the hidden Warmbly-only pipelines come back, and HubSpot keeps everything that was written there. Switching back to the same HubSpot account later picks up the same records rather than duplicating them. + + + + ## Self-hosting A self-hosted instance uses its own HubSpot app. Create one in a HubSpot developer account, then: @@ -220,10 +324,18 @@ A self-hosted instance uses its own HubSpot app. Create one in a HubSpot develop The consumer runs the minute-by-minute pull and the sync queue, so HubSpot mode needs no inbound traffic from HubSpot. Webhooks are optional: subscribe the app to `contact.creation`, `contact.propertyChange`, `contact.deletion`, `deal.creation`, `deal.propertyChange` and `deal.deletion` with the target URL `/api/v1/integrations/hubspot/webhooks`, and a change is picked up within seconds instead of within the minute. Deliveries are verified with HubSpot's `X-HubSpot-Signature-v3` signature over `BACKEND_PUBLIC_URL` and only make the next read come sooner; Warmbly always reads the record from HubSpot itself, and removes a deal or task only when HubSpot answers that it is gone. + + + + ## Moving a workspace A [workspace export](/guides/workspace-export-import/) carries the CRM mode, its setup choices, the owner matches, which Warmbly record is which HubSpot record, and each contact's HubSpot details, so the destination keeps updating the same HubSpot records instead of creating duplicates. The sync queue and the pull checkpoints stay behind; the destination starts its own pull. Reconnect HubSpot on the destination unless it uses the same HubSpot app and the archive carried credentials. + + + + ## FAQ **Does it work with free HubSpot?** Yes. Contacts, companies, deals, tasks, notes, owners and custom contact properties are all part of HubSpot's free CRM. The list and email-reading permissions are optional, so an account that cannot grant them still connects; only list import is unavailable without the list permission. @@ -238,6 +350,8 @@ A [workspace export](/guides/workspace-export-import/) carries the CRM mode, its **Does every member need a HubSpot seat?** No. Members without a matched owner still see deals, tasks and notes; they cannot be assigned HubSpot deals or tasks, and their mailboxes log email under the contact's owner. + + ## Next steps diff --git a/docs/content/docs/guides/inbox-tagging.mdx b/docs/content/docs/guides/inbox-tagging.mdx index 465c0052a..a33bb702a 100644 --- a/docs/content/docs/guides/inbox-tagging.mdx +++ b/docs/content/docs/guides/inbox-tagging.mdx @@ -3,20 +3,76 @@ title: "Automatic inbox tagging" description: "Label and score inbound mail as it arrives, keep automated mail out of the inbox, and let a confident verdict hold, stop, follow up or suppress. Optional, off by default." --- +Automatic tagging reads each inbound message, files it under workspace labels and scores how much it deserves your attention. It is optional and off until an operator turns it on. + +## Turn on tagging + + + + + +An operator sets `TYPESAFE_API_KEY` to a key from typesafe.ai. + + + + + +The operator sets `INBOX_TAGGING_ENABLED=true`, since tagging stays off even when a key is present. + + + + + +Open **Settings > Inbox tagging** to see every decision and what it did. + + + + + +After watching it for a week, turn on the suppression under **Settings > Sending > Classified replies** if you want it. + + + + + +## Good to know + +- Classification sends message content to TypeSafe, so nothing is sent until an operator adds a key and switches the feature on. +- Mail nobody wrote, such as sign-in codes, receipts, bounces and auto-replies, moves to the **Automated** view. +- Automated mail that needs your action stays in the inbox, labelled **Action required**. +- When the classifier is unsure, the thread is labelled **Needs review** and stays in the inbox. +- Holding, stopping and opening a task are on by default; suppressing a sender is off until you turn it on. + + + +## What tagging is and what it sends out + Automatic tagging reads each inbound message and files it under workspace labels, then scores how much it deserves your attention. It is optional and off until an operator turns it on. Classification sends message content to TypeSafe, so it is opt-in twice: an operator supplies a key, and separately switches the feature on. An instance that does neither never calls TypeSafe and behaves exactly as it did before. Other optional AI features can also send content to their configured provider; see [data control](/development/data-control/). + + + + ## What it does It writes labels and a relevance score for every inbound message, and moves mail nobody wrote (sign-in codes, receipts, notifications, bounces, auto-replies) out of the inbox into the **Automated** view. Automated mail that needs your action, such as a failed payment or a suspended account, stays in the inbox labelled **Action required**. That part needs nothing from you beyond turning the feature on. It can also act on a confident verdict: hold a contact who said not now, stop a contact who declined, open a task when someone asks for a call, and suppress a sender who asked to be removed. Each of those is its own switch under **Settings > Sending > Classified replies**. The three reversible ones are on from the start; the suppression is off until you turn it on, because it is the one the system cannot undo. See [Acting on a verdict](#acting-on-a-verdict). + + + + ## Nothing to set up The whole label set is created when a workspace is created (and, on an instance that turned the feature on later, within the hour by the follow-up sweep), so the inbox can filter for **Unsubscribe** before anyone has asked. The premade **Views** in the inbox rail (Action required, Hot leads, Needs a reply, Follow up, Declined, Automated) are built on those labels and fill as mail is classified; see [Unified inbox](/guides/unibox/). Every label has a hover explanation, so nobody has to learn the taxonomy first. + + + + ## The labels Labels are plain words, so anyone on the team can read them without learning a taxonomy first. They are ordinary workspace labels: the inbox filters on them like any label you made yourself. A workspace that already had a label with the same name keeps it and the automatic one files under it. @@ -42,6 +98,10 @@ The classifier answers more questions than this (whether the sender is the decis **Needs review** means the classifier was not sure. See below. + + + + ## Automated mail stays out of the inbox Sign-in and verification codes, routine security, account and billing notices, receipts, newsletters, bounces and auto-replies are not mail anyone has to answer. When the verdict is that no person wrote a message, its conversation leaves **Inbox**, Unread, every mailbox and tag view and the unread badge, and is listed under the **Automated** view instead. **All mail** still shows everything. @@ -50,6 +110,10 @@ Only a confident verdict moves a conversation: a message the classifier was unsu Common senders and headers (no-reply addresses, bulk mail, out-of-office subjects, delivery failures) are decided offline. Everything else is judged by the model. + + + + ## Mail that needs your action Mailboxes used only for campaigns still receive mail a person has to deal with: a failed payment for the mailbox's subscription, a suspended or restricted account, a suspicious sign-in, a sending limit or policy notice from the provider, a domain or service about to expire. Nobody logs into those mailboxes one by one, so if that mail went to Automated with the receipts it would never be seen. @@ -67,6 +131,10 @@ A notification recognised offline by its sender alone costs one small call for t It is on by default for every workspace. To turn it off, clear **Keep mail that needs action in the inbox** under **Settings > Sending > Automated mail**; notifications then go to Automated as before and no call is made for them. To file other automated mail your own way, ask one of [your own questions](#your-own-questions) of notifications too. + + + + ## Follow-ups: who owes whom a reply Three labels answer the question a pipeline actually turns on, which is not "what is this message" but "what have I dropped". @@ -95,12 +163,20 @@ To recompute on demand (this checks every conversation in one run and leaves the warmblyctl inbox-tag follow-ups --org you@example.com ``` + + + + ## Relevance and priority Each message gets a score from 0 to 100, and a priority of `now`, `today`, `whenever` or `ignore`. Sorting the inbox by relevance puts the person who said yes above the person who is thinking about it, above a bounce. The score is arithmetic over the answers, computed in Warmbly rather than asked. Asking for a single "how relevant is this" rating hides four independent judgments inside one question, and the model correctly refuses those: it returns a flat distribution at zero confidence. + + + + ## Acting on a verdict Four actions, each behind its own switch under **Settings > Sending > Classified replies**. The hold, the stop and the task are on by default; the suppression is off. Only a confident human reply may act: a verdict marked **Needs review**, a bounce, an autoresponder or a cold pitch never does, whatever its signals say. The intent has to clear the same `0.70` floor the labels use, because an action on an intent the labels did not trust is an action on a coin toss. @@ -118,6 +194,10 @@ Your own questions can act too; see [Your own questions](#your-own-questions). Every action is recorded on the verdict, so the review page shows what was done next to the answer that caused it, and the **Actions taken** count at the top tells you how often the switches fire. Actions run on mail as it arrives; a backfill over history labels and never acts. + + + + ## Your own questions The built-in labels answer what every workspace needs to know. Anything more specific to yours, you can ask yourself under **Settings > Sending > Tagging languages and questions**. For example: "The sender says there is no suitable position right now but they will get back later", labelled **Later maybe**, holding the contact for 60 days. @@ -135,6 +215,10 @@ A question can also be asked of automated notifications: tick **Also ask about a Your questions ride in the same call as the built-in ones, so they cost input tokens on every classified message but never a second request. A workspace can ask up to 10. They add labels and nothing else to a verdict: the kind, the intent and the relevance score are calibrated on the built-in questions alone and are never moved by yours. The labels are created the first time they apply, or within the hour by the hourly sweep. Questions are part of the workspace settings, so they travel with a [workspace export](/guides/workspace-export-import/). + + + + ## Tagging languages Tagging reads English, German, French, Spanish, Italian and Dutch replies out of the box. If your mail also arrives in other languages, add them under **Settings > Sending > Tagging languages and questions**. Nothing about a language is used until a workspace adds it: a language nobody on your team writes in only adds patterns that could misread someone's mail, so leave it off. The setting changes inbox tagging only; campaign reply handling reads replies the same way whatever it says. @@ -152,6 +236,10 @@ The reading before any call covers: Tagging languages are part of the workspace settings, so they travel with a [workspace export](/guides/workspace-export-import/). + + + + ## What else reads the verdict Two other features reuse a stored verdict rather than paying for a second judgment: @@ -159,6 +247,10 @@ Two other features reuse a stored verdict rather than paying for a second judgme - **Campaign reply handling.** The reply classifier that drives `stop_on_reply` and the reply branches reads the stored kind and intent when one exists, so an interested reply routes as positive and a declined one as negative with the classifier's real confidence. The intent itself is also stored on the contact's progress, and a sequence can branch on it with the `reply_intent` condition (see [Campaigns](/guides/campaigns/)). Without tagging, the classifier asks TypeSafe one three-way question instead, and without a key it falls back to the platform LLM as before. - **The inbox agent.** A reply the verdict shows as closed (Not interested, Unsubscribe, Wrong person, Not now), a one-line acknowledgement, a legal threat or a message that needs a person to read it is not drafted, so no credits are spent answering it. + + + + ## Confirming an away message's return date When an [out-of-office reply holds a lead](/guides/campaigns/#out-of-office-and-pausing-one-lead), the day it resumes comes from rules that read the return date out of the away message. Rules can pick the wrong date near a cue: `ab dem 4.9. nicht im Büro` names the first day away, not the last. So when tagging is on and the hold is on, an away message whose date the rules read is asked one more yes/no question: whether the phrase the date came from (`ab dem 4.9.`, `bis KW 41`, `until 12 September`) says when the sender will be back. @@ -169,12 +261,20 @@ Below `0.40` the hold uses the workspace's fallback instead of the date. That is An away message recognised offline costs one small call carrying only the message and the phrase, and only when the rules found a date and the workspace holds on out-of-office. When the model decides the kind, the question rides in the same call as the rest. A backfill never asks it, since it labels history and holds nobody. The review page shows the date and the answer next to the verdict, struck through when the hold used the fallback. + + + + ## When it declines to decide Every classification carries a confidence. If the message kind is below 0.70, the thread is labelled **Needs review**, no other label is applied, and the conversation stays in the inbox. If the kind is confident but the intent is below 0.70, the trusted kind and relevance remain, the uncertain intent label is omitted, and **Needs review** is added. That floor is not caution for its own sake. A low-confidence answer is not merely uncertain, it is not reproducible: the same ambiguous message run three times returned a different winning label each time while confidence stayed near 0.2. A label that would not survive being asked again is not one to file mail under. + + + + ## What it never asks Anything Warmbly already knows is established in code and never sent as a question: which direction a message travelled, which mailbox it belongs to, which campaign it answers, whether the sender is already suppressed. @@ -185,12 +285,20 @@ This matters more than it sounds. Given only a message body, the model classifie Common out-of-office subject prefixes and automated sender addresses are checked offline. When one is decisive, the kind is not asked; only a notification is still asked whether it needs your action, and an away message whether its [return date](#confirming-an-away-messages-return-date) is the day the sender is back. The review page shows which verdicts were decided offline and which came from the model. + + + + ## Follow-ups without TypeSafe With no `TYPESAFE_API_KEY`, Warmbly can still compute **Follow up** locally from sent-message dates. It makes no external call. **Needs reply** and **Gone quiet** also need a trusted stored classification so an automated message is not mistaken for a person and a weak intent is not treated as interest, and without classification nothing leaves the inbox as automated. The key and switch add message classification. When both are unset, automatic inbox tagging sends no message content to TypeSafe. Other optional AI features use their separately configured provider; see [data control](/development/data-control/). + + + + ## Turning it on Two environment variables: @@ -202,6 +310,10 @@ INBOX_TAGGING_ENABLED=true # off by default even when a key is present Then **Settings > Inbox tagging** shows every decision: the labels, the relevance, the confidence, where the verdict came from, and what it did. Watch it for a week before enabling the suppression under **Settings > Sending > Classified replies**; the reversible actions are already on and every one of them is visible on the Leads tab or the Tasks page. + + + + ## Backfilling mail you already have A classifier that only sees new arrivals is no use on the day you switch it on: the inbox you want sorted is the one already sitting there. Run it over your history: @@ -236,8 +348,11 @@ warmblyctl inbox-tag backfill --org you@example.com --days 30 --recheck-notifica Only messages stored as a notification by a verdict that never asked the action check are covered, and each is asked again, so the ones that need action return to the inbox labelled **Action required**. The command refuses when **Keep mail that needs action in the inbox** is off. A notification with no text to read is skipped. A message whose call fails is offered again by the next run, or by a plain backfill. Like any backfill, it labels, moves and never notifies. -## What it costs + + + +## What it costs One call contains every question for a message and currently uses about 1,300 input tokens on the recorded fixture set. A notification recognised offline costs one smaller call, for the action check and any of your questions asked of notifications, and nothing when neither is on. An away message recognised offline costs one small call when its return date is checked, and nothing otherwise. TypeSafe evaluates the questions in parallel, but each question still contributes tokens, so check its current pricing before a large backfill. Output tokens are not billed. @@ -251,6 +366,8 @@ Incoming mail is still saved during the pause. Offline rules and already stored A message with no Message-ID is skipped. A database claim prevents two concurrent deliveries of the same Message-ID from making duplicate calls, and an abandoned claim becomes eligible for retry after 15 minutes. + + ## Related guides - [Unified inbox](/guides/unibox/): where the labels appear and how to filter on them diff --git a/docs/content/docs/guides/index.mdx b/docs/content/docs/guides/index.mdx new file mode 100644 index 000000000..4367515b2 --- /dev/null +++ b/docs/content/docs/guides/index.mdx @@ -0,0 +1,79 @@ +--- +title: Getting started +description: Connect a mailbox, warm it up and send your first campaign. +--- + +Warmbly sends cold email from mailboxes you own. It warms them up, spreads your campaigns across them, and brings every reply into one inbox. + + + + + +### Connect your mailboxes + +Open **Accounts** and choose **Add account**. Connect Google Workspace or Microsoft 365, or any host over SMTP and IMAP. + +![Add account: Google, Microsoft, any SMTP and IMAP host, a file import, or an inbox vendor](/guides/add-account.webp) + +[More about mailboxes](/guides/mailboxes/) + + + + + +### Check your domains + +**Sending domains** shows whether SPF, DKIM and DMARC are published for every domain you send from. + +![Sending domains: each domain with its mailboxes, authentication and tracking host](/guides/sending-domains.webp) + +[More about sending domains](/guides/sending-domains/) + + + + + +### Turn on warmup + +New mailboxes start with warmup off. Switch it on from the mailbox's **Warmup** tab. Warmup is part of every paid plan. + +![A mailbox's Warmup tab: participation, today's sends against the ramp target, pool standing and the ramp settings](/guides/mailbox-warmup.webp) + +[More about warmup](/guides/warmup/) + + + + + +### Send a campaign + +Open **Campaigns**, choose **New campaign**, write the emails, add contacts and pick the sending hours. + +![The Steps canvas: the first email, then follow-ups that reply in the same thread, each with its wait on the line](/guides/campaign-steps.webp) + +[More about campaigns](/guides/campaigns/) + + + + + +### Answer the replies + +Every reply, from every mailbox, lands in the **Inbox**, labelled by what the person wants. + +![The unibox: folders, mailboxes and labels on the left, the conversation list, and the open thread](/guides/unibox.webp) + +[More about the inbox](/guides/unibox/) + + + + + +## Next + + + Turn a reply or a booked meeting into a deal, a task or a Slack message. + Contacts, deals, tasks and meetings in one place. + See where your mail lands and what to fix. + Sends, opens, replies and bounces over time. + diff --git a/docs/content/docs/guides/integrations.mdx b/docs/content/docs/guides/integrations.mdx index 30512c6f8..a08e2f561 100644 --- a/docs/content/docs/guides/integrations.mdx +++ b/docs/content/docs/guides/integrations.mdx @@ -3,12 +3,54 @@ title: Integrations description: "Connect third-party tools over OAuth and send data out via webhooks." --- +Integrations connect Warmbly to the rest of your tools. When a reply lands, a meeting is booked or an email bounces, your CRM, chat channel or automation tool hears about it. + +## Connect an integration + + + + + +Open **Integrations** and find the app in **Discover**, **All apps** or a category, or search for it. + + + + + +Select **Connect** on its card or its page. + + + + + +Sign in to the provider for a one-click connection, or paste an API key or webhook URL when asked. + + + + + +## Good to know + +- Credentials are sealed with envelope encryption before they touch the database. +- An expired or revoked token moves the connection to "reconnect required", and one click fixes it. +- A workspace uses one verification service at a time. +- Outbound events are HMAC-SHA256 signed so receivers can verify them. +- HubSpot or Pipedrive can become the workspace's whole CRM, one at a time. + + + +## About the Integrations page + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. Integrations push Warmbly events into the rest of your stack: a reply lands, a meeting is booked, an email bounces, and your CRM, chat channel, or automation tool hears about it. The Integrations page is one directory for everything that connects to Warmbly: the built-in integrations below, apps other developers have published, and the tools to build your own. State stays live, so a connection needing reauthorization surfaces without a refresh. + + + + ## Finding an integration The sidebar holds **Discover**, **All apps**, **Connected**, one view per category, **Community apps** and **Build your own**; on a phone the same views sit in a row of tabs. @@ -29,6 +71,10 @@ On **All apps**, **Connected**, **Community apps** and each category, the same b The active filters show as chips you can clear one at a time or all at once. Search, sort, filters and view are kept in the page address, so a filtered view can be bookmarked or shared with a teammate. + + + + ## Supported providers | Provider | Category | Connect | What it does | @@ -78,6 +124,10 @@ An account with nothing left is only visible when a check is refused, so Warmbly A workspace uses one verification service. Connecting a second one is refused while the first is connected, because which one ran would otherwise be decided by connection order alone and every check would quietly move to a different bill. Disconnect the current service first, then connect the new one. + + + + ## Community apps A community app is an [OAuth app](/api/oauth/) another team built on the Warmbly API and published. Opening one shows who built it, what it does, how many workspaces use it, and every permission it can ask for. @@ -105,10 +155,18 @@ Any change to the listing, or to the app's name, logo, website or permissions, r A listing's link must be 3 to 48 lowercase letters, numbers or single dashes, and the names of the built-in integrations are reserved. The tagline is at most 120 characters and the description 2,000. Every link must be `https`. + + + + ## HubSpot or Pipedrive as your CRM HubSpot and Pipedrive can do more than receive contacts: in [HubSpot mode](/guides/hubspot/) or [Pipedrive mode](/guides/pipedrive/) one of them becomes the workspace's CRM. Deals, pipelines, tasks and notes are the CRM's records written through from Warmbly, sends, replies, bounces, unsubscribes and meetings are logged on the contact, and a deal opened or a lifecycle stage (in Pipedrive, a label) reached there stops the contact's campaigns. Set it up from **Integrations > HubSpot** or **Integrations > Pipedrive**. A workspace runs on one CRM at a time. + + + + ## CRM field mappings A mapping decides how a Warmbly contact projects onto provider fields. Every connection ships a sensible default: @@ -131,6 +189,10 @@ In HubSpot or Pipedrive mode, contacts sync on their own with a field map of the Empty values are dropped before the upsert, so a missing first name never blanks an existing one in your CRM. + + + + ## Webhook delivery Outbound events are **HMAC-SHA256 signed** so receivers can verify them. Reveal the connection's `whsec_` signing secret in its settings and check the signature header on every request. The signed body carries a stable delivery id, event type, version, timestamp, and full event data. @@ -143,6 +205,10 @@ URLs are validated before storage under the same policy as customer webhooks: th **Inbound webhooks** work the other way: Calendly and Cal.com call into a URL Warmbly mints for you, landing booked, rescheduled, and canceled calls on the contact timeline in real time. Rotate the secret from connection settings if exposed. + + + + ## Feeding automations Integrations are most useful as the actions at the end of an [automation](/guides/automations/): post to Slack or Discord, upsert into a CRM, or fan out to Zapier, Make, n8n, or any signed webhook. @@ -155,6 +221,8 @@ Actions run in the background, so a slow third party never blocks the event that You can filter when an action runs, commonly firing only on a `positive` reply or above a minimum classifier confidence. Beyond events, you can push a batch of contacts into a connected CRM on demand, with each record reporting its own result. + + ## Next steps diff --git a/docs/content/docs/guides/mailbox-import.mdx b/docs/content/docs/guides/mailbox-import.mdx index 2a7ffffe7..647e77978 100644 --- a/docs/content/docs/guides/mailbox-import.mdx +++ b/docs/content/docs/guides/mailbox-import.mdx @@ -3,6 +3,50 @@ title: Import mailboxes description: "Connect hundreds or thousands of mailboxes from a spreadsheet, a vendor export, or a pasted list." --- +**Import mailboxes** connects many mailboxes in one go, from the file your inbox vendor or your previous sending tool gave you. Only an address and a password are required: servers, ports and encryption are found for you. + +## Import a file + + + + + +Open **Accounts**, choose **Add account**, then **Import mailboxes**. + + + + + +Upload a CSV, TSV or XLSX file, or paste a list such as `address:password`, one mailbox per line. + + + + + +Check the preview: each column is matched for you and can be reassigned by hand. + + + + + +Start the import. It runs on the server, so closing the tab does not stop it. + + + + + +## Good to know + +- One import takes up to 5,000 mailboxes, from a file of up to 10 MB. +- Google mailboxes need a 16-letter app password. Microsoft 365 and Outlook.com mailboxes connect with Microsoft sign-in instead. +- To skip passwords for a whole domain, [connect a whole Google Workspace domain](#connect-a-whole-google-workspace-domain) or [a whole Microsoft 365 organization](#connect-a-whole-microsoft-365-organization). +- **Inbox vendor** reads the mailboxes straight from the vendor you bought them from, with an API key. +- Failed rows are grouped by cause, and you can fix and retry them for 7 days without uploading again. + + + +## What an import does + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. **Import mailboxes** connects many mailboxes in one go. Open **Accounts**, choose **Add account**, then **Import mailboxes**. It takes the file your inbox vendor or your previous sending tool gave you, works out what each column means and where each domain's mail is hosted, and connects the rows in the background. @@ -17,6 +61,10 @@ Three other sources skip the file entirely, each reached from the same **Add acc All three create the same kind of import as a file: it runs in the background, shows the same progress, and groups failures by cause. + + + + ## What it reads - **A file**: CSV, TSV or XLSX (the active sheet), up to 10 MB. A CSV may separate its columns with commas or semicolons (the separator spreadsheet apps use where the comma is the decimal mark); the header line decides which. @@ -31,6 +79,10 @@ One import takes up to 5,000 mailboxes. Split a larger file and import the rest The first row is treated as a header unless it holds an email address. **First row is a header** in the dialog overrides the guess. + + + + ## Columns Every column is matched to a field automatically. The first of these that applies wins: @@ -63,6 +115,10 @@ Every column can be reassigned by hand, or set to be ignored. Password columns a Security columns accept `SSL`, `TLS`, `SSL/TLS` and `STARTTLS`, and the `true`/`false` SSL columns some exports use. Without one, security follows the port. + + + + ## Where the servers come from Servers are found once per domain, from DNS and the provider's own published settings, in this order: @@ -77,6 +133,10 @@ The preview lists each domain with its provider, the servers found, and whether Each mailbox is labelled with where it is hosted and how it signs in. The [API](/api/reference/mailboxes/) returns these as `mail_host` and `auth_method`; a mailbox whose host is not recorded yet, such as one connected before the labels existed, is classified in the background from its domain. + + + + ## What each provider needs | Host | The password must be | Where to get it | @@ -109,6 +169,10 @@ A Microsoft 365 admin can grant consent once for the whole organization, after w Proton Mail offers no IMAP or SMTP sign-in to other apps; only its Bridge does. Proton rows are flagged as unable to connect. On a [self-hosted instance](/development/install/) whose worker runs on the machine the Bridge is on, map `smtp_host` and `imap_host` columns (and ports) to the Bridge and use the Bridge's own password. See [local mail relays](/guides/mailboxes/#local-mail-relays-proton-bridge). + + + + ## Import from an inbox vendor Choose **Add account**, then **Inbox vendor**, pick the vendor and paste an API key. That is all any vendor asks for. Warmbly checks the key with the vendor before saving it, lists the mailboxes the key reaches (marking the ones already in the workspace), and imports the ones you pick, or every one not yet connected. @@ -134,6 +198,10 @@ One vendor account lists up to 10,000 mailboxes. - **A refused key marks the connection.** A key the vendor accepts but that reaches no workspace is refused with `mailbox_vendor_no_workspace`, since there is nothing to list. When the vendor stops accepting the key, the connection shows as invalid with the vendor's reason, and rows that needed it fail as `vendor_unauthorized` until the key is updated. - **Deleting the connection keeps the mailboxes.** They stay connected and keep sending; they are no longer reconnected automatically when the vendor rotates a password. + + + + ## Connect a whole Google Workspace domain A Google Workspace super admin can authorize Warmbly for the whole domain with domain-wide delegation. Every mailbox on the domain then connects through that one grant: no app passwords, nothing that expires when someone changes a password, and nothing for each mailbox owner to do. It is the recommended way to connect Workspace mailboxes. @@ -179,6 +247,10 @@ The grant is checked again every hour. When Google stops honoring it (the entry Connecting mailboxes through a grant and deleting a grant both reach a whole domain's mail, so like recording one they need a recent confirmation of your sign-in. + + + + ## Connect a whole Microsoft 365 organization A Global Administrator can approve Warmbly once for the whole Microsoft 365 organization. Mailboxes then connect through Microsoft Graph with application permissions: no sign-in per mailbox. It is the recommended way to connect Microsoft 365 mailboxes. @@ -209,6 +281,10 @@ Microsoft's newer RBAC for Applications does the same with a management scope an Pick users from the directory, or connect every enabled one not yet in the workspace. Only users with a mail address are listed. A mailbox already here on its own Microsoft sign-in is marked as one to move, as with Google: connecting it puts it onto the grant in place, and connecting everyone includes it. Moving is optional for Microsoft mailboxes; their own sign-in keeps working. Each mailbox is checked against Microsoft Graph as it connects; a disabled account, or a user without an Exchange Online license, has no mailbox to connect and is refused. As with Google, no token is stored: the backend mints one each time a worker needs it. The grant is checked every hour, its mailboxes stop when the consent is withdrawn and restart when a check succeeds again, a check that gets no answer from Microsoft changes nothing, and deleting it stops every mailbox it connected. + + + + ## When a file meets an admin grant A file or pasted list does not need to know about grants. When an active grant covers a row's domain, the row connects through it: @@ -218,6 +294,10 @@ A file or pasted list does not need to know about grants. When an active grant c The same holds for rows from an inbox vendor. A grant made after the import also counts: rows still waiting for Google or Microsoft sign-in on a domain it covers are queued again and connect through it within a minute, with no need to import them again. The preview marks these rows, and each connected mailbox is labelled with the sign-in method `delegated`. A grant that stops working fails its rows as `mailbox_grant_inactive`; check the grant, then retry them. + + + + ## Mailboxes already connected An address already in the workspace, connected by anyone, is matched case-insensitively. **If a mailbox is already connected** decides what happens: @@ -227,6 +307,10 @@ An address already in the workspace, connected by anyone, is matched case-insens A mailbox connected with Google or Microsoft sign-in only takes the settings; there is no password to replace. When an active grant covers its domain, the row moves it onto the grant instead, in place, keeping its history, campaigns and warmup (see [moving off per-mailbox Google sign-in](/guides/mailboxes/#moving-off-per-mailbox-google-sign-in)); **Skip** leaves it as it is. When the same address appears twice in one file, only the first row is imported and the later ones are skipped as **Listed twice in the file**. + + + + ## Settings for every mailbox The dialog sets these for every mailbox the import connects or updates. A mapped column wins over them for its own row. @@ -239,6 +323,10 @@ The dialog sets these for every mailbox the import connects or updates. A mapped Tags are added to the ones a mailbox already has. A tag named in the `tags` column that does not exist yet is created. A setting that cannot be applied never fails its row: the mailbox is connected and the row's message says what did not apply. A value that cannot be read at all, such as text in a limit column, fails that row before it is tried. See [sending behavior](/guides/sending-behavior/) and [warmup](/guides/warmup/) for sensible values. + + + + ## Tracking domains and root redirects The review step makes two choices for every domain in the import at once, and says how many of them an inbox vendor sets up for you. Both are applied as the import connects the domain's mailboxes, and both can be changed later on [sending domains](/guides/sending-domains/). @@ -250,6 +338,10 @@ With more than one domain, **Change per domain** opens a list of every domain wi A setting that cannot be applied does not fail the row: the mailbox connects and the row's message says what did not apply. In the API these are the `tracking_domains` and `redirects` objects in `options`, each keyed by domain, up to 500 domains each. + + + + ## While it runs The import runs on the server. Closing the tab does not stop it, and everyone in the workspace who can manage mailboxes sees its progress live, with the mailbox list filling in behind it. A server restart does not lose rows either: a row being connected at the time is picked up again, and a mailbox it had already created is finished with the import's settings rather than skipped as one that was already there. **Recent imports** in the dialog, and the **Imports** button on the Accounts page, reopen one later. @@ -262,6 +354,10 @@ Rows past the workspace's [mailbox allowance](/guides/mailboxes/#mailbox-allowan Cancelling stops the rows not yet started. Rows already connecting finish. + + + + ## When rows fail Failures are grouped by cause, each with the fix. The same keys appear as `cause` on each row in the API. @@ -327,6 +423,10 @@ Then fix them without re-uploading: A retry is possible for 7 days after the import finishes. After that the row's credentials are gone and it has to be imported again. + + + + ## What is kept - Credentials of rows that did not connect are kept, sealed with the workspace's own encryption key, for 7 days after the import finishes so a retry works, then deleted. @@ -337,10 +437,16 @@ A retry is possible for 7 days after the import finishes. After that the row's c The saved column mappings and vendor connections travel with a [workspace export](/guides/workspace-export-import/). Imports themselves do not, and neither do admin grants: a grant is recorded only after the workspace proves the domain or organization on the instance it runs on, and an archive proves nothing. The mailboxes a grant connected do travel. On the destination, make the grant again, then pick those users in its directory (they are listed as already connected, so connecting every user not yet here skips them), or import a file of their addresses with **Update** for existing mailboxes. Each one relinks the mailbox that arrived, with its history and settings, rather than adding a second one. + + + + ## API The import is available to a signed-in session under `/emails/imports`, never to an API key, because it carries passwords. Vendor connections (`/emails/vendors`), admin grants (`/emails/grants`) and sending domains (`/emails/domains`) are session-only for the same reason. See [endpoints](/api/endpoints/#jwt-only) for the routes. `POST /emails/onboarding/smtp-imap/bulk` remains for connecting up to 50 SMTP and IMAP mailboxes in one synchronous call. + + ## Related guides diff --git a/docs/content/docs/guides/mailboxes.mdx b/docs/content/docs/guides/mailboxes.mdx index 7d3cbd01f..810923737 100644 --- a/docs/content/docs/guides/mailboxes.mdx +++ b/docs/content/docs/guides/mailboxes.mdx @@ -3,6 +3,49 @@ title: Mailboxes description: "Connect sender accounts, providers, and how they are assigned to workers." --- +A mailbox is a sender account you connect to Warmbly. Every warmup message and campaign email goes out through one you own. + +![The Accounts page: every connected mailbox with its status, cold sends today, warmup progress, inbox rate and health](/guides/accounts.webp) + +## Connect a mailbox + + + + + +Open **Accounts** and choose **Add account**. + + + + + +Pick **Google**, **Microsoft**, or **Other (SMTP / IMAP)** for any other host. + + + + + +On Google Workspace or Microsoft 365, choose the administrator's grant: it connects every mailbox on the domain at once, with no password and no sign-in each. + + + + + +![Add account: Google, Microsoft, any SMTP and IMAP host, a file import, or an inbox vendor](/guides/add-account.webp) + +To bring in many at once, choose **Import mailboxes**: up to 5,000 from a CSV, TSV or XLSX file, or a pasted list. + +## Good to know + +- A mailbox sends at most `50` cold emails a day by default. New mailboxes start on a sending plan of about `30` a day. +- Mailboxes are unlimited on every paid plan, with a fair-use allowance of one mailbox for every send a day the plan includes. +- When a provider stops accepting a mailbox's sign-in, the mailbox stops sending and its drawer shows the fix. +- You never pick a sending worker. Warmbly assigns one. + + + +## How the list reports health + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. A mailbox is a sender account you connect to Warmbly. Every warmup message and campaign email goes out through one you own. @@ -13,12 +56,20 @@ The Accounts list checks mailbox health and, on linked self-hosted instances, Cl New mailboxes default to diagnostic participation **off**. The organization-authorized mailbox PATCH exposes `test_mode` (`off`, `diagnostic`, `legacy`), separate `test_send_enabled` and `test_receive_enabled` flags, and optional positive `shared_daily_limit`/`rolling_recipient_limit`. These are API controls, not new dashboard switches. Diagnostic mode sends disclosed tests but does not manufacture reads, stars, importance or spam rescue. Existing upgraded mailboxes with NULL participation retain legacy behavior until explicitly changed. Set `test_mode=off` to stop future diagnostic send/receive admission; current protocol-2 workers also recheck queued sends/actions. Already accepted old-worker commands or a provider send underway cannot be recalled. A local Cloud alias unlink stops local authority, not an independently cloud-owned mailbox or provider grant: pause/remove that mailbox on Cloud separately. See [send safety, repair evidence and rollout](/development/send-safety/). + + + + ## Filtering accounts Use the **Tags** dropdown above the mailbox list to show accounts with a particular tag. The selection is kept in the page URL and remembered for your user and workspace in the browser tab, so refreshing, opening a bookmark, or visiting another page and returning keeps the filter. Choose **All accounts** to clear it. A tag that no longer exists in the current workspace falls back to **All accounts**. Without an explicit column sort, the Accounts list groups the loaded mailboxes by their complete set of tags, both in **All accounts** and when filtering by a specific tag. Mailboxes with **Tag A + Tag B** stay together even when those tags were assigned in a different order, separately from mailboxes with only **Tag A**. Groups follow the workspace's tag order, with untagged mailboxes last; mailboxes within a group are ordered by email address. Selecting a tag narrows the list without changing this order. Each mailbox appears only once. Selecting a column sort takes priority over grouping and remains selected when changing the tag filter. + + + + ## Connecting an account Open **Accounts** and choose **Add account**. Google and Microsoft each have one entry, and choosing it asks how to connect: @@ -156,6 +207,10 @@ Both are about the same thing: the credentials never leave the machine, so there Set the Bridge's own username and password, which it shows in its interface, not your Proton account password. The same applies to any relay on the machine: a Dovecot sharing a host with its worker, or a local sink used for testing. + + + + ## Mailbox allowance Mailboxes are unlimited on every paid plan. What keeps that honest is a fair-use allowance derived from the plan's sending volume: one mailbox for every send a day the plan includes. @@ -183,6 +238,10 @@ Nothing is ever removed for being over the allowance. A workspace that moves to There is no daily cap on how many mailboxes you connect: a Business workspace can connect thousands in one afternoon, and the [mailbox import](/guides/mailbox-import/) exists for exactly that. + + + + ## Connecting many mailboxes at once Choose **Import mailboxes** in the connect dialog to connect up to 5,000 mailboxes from a CSV, TSV or XLSX file, a vendor export, or a pasted list of `address:password` pairs. Only an address and a password are needed: the columns are recognised and each domain's servers are found from DNS. Microsoft 365 and Outlook.com rows wait for a Microsoft sign-in each. Google rows use an app password, an admin grant, or Google sign-in where enabled. The import runs on the server, verifies every credential before saving it, and groups whatever fails by cause with the fix. @@ -191,6 +250,10 @@ The same dialog imports straight from an [inbox vendor](/guides/mailbox-import/# See [Import mailboxes](/guides/mailbox-import/) for the columns, what each provider needs, and what is kept, and [sending domains](/guides/sending-domains/) for setting a tracking host and a root redirect per domain. + + + + ## Reconnecting an account When the provider stops accepting a mailbox's stored credential (a password change, a revoked grant, an expired app password), the mailbox is taken out of sending and syncing and its drawer shows the reason under **Needs attention**, with the fix right on the error: @@ -203,6 +266,10 @@ A successful reconnect stores the new credential, clears the authentication erro Reconnecting never counts against the mailbox allowance, so a workspace at its cap can still fix a broken mailbox. A mailbox whose sign-in is held by Warmbly Cloud is reconnected from your cloud workspace instead; the button says so if you try locally. + + + + ## What gets synced Connecting a mailbox does two things: it imports the mailbox's recent history, and from then on it follows new mail as it arrives. Both are the same on every provider. @@ -245,6 +312,10 @@ The same setting is `PUT /emails/:id/sync` in the [API](/api/endpoints/) and `wa Only two patterns deactivate a mailbox's sync, and both mean something is wrong upstream: a flood (thousands of new messages in a single hour, more than any real inbox produces) or exceeding the daily budget on three of the last seven days. The mailbox then shows the reason under **Needs attention**; fix what is delivering that volume into it, or ask your administrator to raise the budget, and reactivate it. + + + + ## Sending controls Set these on the mailbox's **Settings** tab. They apply to the next scheduled send, not retroactively. @@ -369,6 +440,10 @@ Clicks are counted per message, not per link: Direct mail reports how many messa Tracking uses the same host as everything else, so a [custom tracking domain](#custom-tracking-domain) on this mailbox applies here too. On an install with no tracking host configured, direct mail goes out untracked rather than carrying a pixel that points nowhere. + + + + ## Warmup New mailboxes should warm up before carrying campaign volume. Defaults are `10`/day starting volume, `+1`/day ramp, and a `40`/day ceiling. @@ -387,6 +462,10 @@ A mailbox marked as a [placement seed inbox](/guides/placement-tests/#your-own-s The model still supports separate free and premium pools, but access is gated to paid workspaces today. + + + + ## Pausing and disconnecting A mailbox can be switched off when you want it to stop without losing its settings, its history, or its place in the fleet. Use **Switch off** on its row's **More** menu, the **Mailbox on** switch on its **Settings** tab, or the selection bar for several at once. Over the API it is `PATCH /emails/{id}` with `status` set to `inactive`, and `active` turns it back on. @@ -423,6 +502,10 @@ If either step cannot finish, because a provider is down or storage is briefly u Closing the whole workspace does the same thing for every mailbox in it, for the same reasons. + + + + ## Worker assignment You never pick a worker. Warmbly assigns each mailbox to a sending worker automatically and can move it later. @@ -443,6 +526,10 @@ Each assigned mailbox counts as one unit against the worker's configurable plann Plans that include reserved sending give your workspace a worker no other customer sends from, so your mailboxes always sign in from an address that is yours alone. It is a preference, not a lock: if that machine goes down your mailboxes keep working on the rest of the fleet and return when a replacement is ready. + + + + ## Resting a tired mailbox Cold sending used to run at full volume until a mailbox crossed a hard health band, with nothing in between. A mailbox showing early fatigue either kept going or was quarantined. @@ -477,6 +564,10 @@ The same hold is available to API keys as `POST /emails/:id/hold` and `POST /ema This is not the same as the risk band that decides which sending worker and IP host a mailbox. A resting mailbox is usually still on a clean worker; it is simply not being offered campaign sends. The mailbox drawer says which state it is in and why. + + + + ## Health The Accounts list groups mailboxes as **Healthy** (sending normally), **Warming** (ramping through warmup), or **Needs attention** (paused, failing, or not sending). Each row shows the mailbox with how it connects and its tags, a **Status** that says what it is doing (**Sending**, **Warming**, **Active** when it does both, **Resting**, or **Idle**, and **Error**, **Off** or **Reconnect** when something stops it), campaign emails sent today against its cap, warmup sent today against the ramp target, the inbox rate, and the health score. Hover a status for the reason, and click any column header to sort. A drop in health between refreshes notifies you rather than waiting to be noticed. @@ -493,6 +584,8 @@ Health is judged only on what happened while the mailbox was in the pool: signal These controls act earlier than the point where providers start penalizing senders. Low complaint rate and low spam placement matter more than maximum throughput. + + ## Related guides diff --git a/docs/content/docs/guides/meta.json b/docs/content/docs/guides/meta.json index de45bb7e5..ea37e0de9 100644 --- a/docs/content/docs/guides/meta.json +++ b/docs/content/docs/guides/meta.json @@ -3,6 +3,7 @@ "description": "Using the Warmbly product", "root": true, "pages": [ + "index", "---Sending---", "mailboxes", "mailbox-import", diff --git a/docs/content/docs/guides/notifications.mdx b/docs/content/docs/guides/notifications.mdx index 7ae9b62e2..4558e7cb1 100644 --- a/docs/content/docs/guides/notifications.mdx +++ b/docs/content/docs/guides/notifications.mdx @@ -3,6 +3,44 @@ title: "Notifications" description: "In-app notification feed and per-category preferences." --- +The bell in the dashboard shows your unread notifications, such as bounces, spam complaints, worker downtime and billing alerts. Each person chooses which ones they get and how. + +## Choose your notifications + + + + + +Open **Settings > Notifications**. + + + + + +Turn each category on or off. Changes save automatically. + + + + + +Under Email delivery, pick how often unread notifications are emailed to you. + + + + + +## Good to know + +- Feeds are per user, not shared across the workspace. +- Health alerts are on by default. **Reply received** is off by default, so a large campaign does not flood the feed. +- Nothing is emailed the moment it happens: unread notifications go out as one bundled email, every 30 minutes by default. +- Anything you read in the dashboard before the email goes out is never emailed. +- Every reply still lands in the [Unibox](/guides/unibox/), even with reply notifications off. + + + +## How the bell and feed work + The bell in the dashboard chrome shows your unread count. Every time you open it, it starts on **Unread**, showing only notifications you have not read, newest first. If there is nothing new, it shows "You're all caught up." Switch to **All** to see recent history, including items already read. Each notification has a title, optional detail, and a link to the thing it is about. Reading one clears it from **Unread** and the count. **Feeds are per user**, not shared across the workspace, so two teammates see different things depending on what they own and what they enabled. @@ -13,6 +51,10 @@ The feed floats above the dashboard instead of being clipped by the header or pa The feed and the bell count update live, without refreshing. + + + + ## Categories and defaults | Category | Group | Default | Raised when | @@ -36,6 +78,10 @@ Health defaults **on** because those alerts are operationally important and low Workspace events never fan out to everyone. Reply and bounce alerts go to the owner of the mailbox or campaign, worker downtime to members who can manage mailboxes, mailbox mail that needs action to members who can both manage mailboxes and use the inbox, a finished placement test to whoever started it, a placement monitor alert to members who can view campaigns, billing to those who can manage billing, and team changes to those who can manage the team. The person who joined is not notified about themselves. + + + + ## Notifications about a message follow it A **Reply received**, **Out-of-office detected** or **Mail that needs action** notification is about one message in the [Unibox](/guides/unibox/), and it stays in step with it: @@ -51,6 +97,10 @@ Even with reply notifications off you never miss anything: every reply still lan Toggle each category under **Settings > Notifications**. Changes save automatically. + + + + ## Channels | Channel | Behavior | @@ -60,6 +110,10 @@ Toggle each category under **Settings > Notifications**. Changes save automatica | **Email** | Your account email, batched by the digest rules below | | **Slack** | Posts to the workspace's connected Slack: the channel set for that category, else the default channel, both chosen under [Slack](/guides/slack/#notifications). A notification for several teammates is posted once. If you linked your Slack account and turned on personal DMs, you also get it as a DM from the bot. With no channel set the toggle still saves, and only personal DMs are delivered. To have every reply posted to a channel as a thread the team can answer from, use the [inbox channel](/guides/slack/#inbox-in-slack) instead | + + + + ## Email digest Nothing is emailed the moment it happens. Three rules govern what reaches your inbox: @@ -78,12 +132,18 @@ Each person also has a rolling 24-hour budget of notification emails (25 by defa For event-specific routing with custom messages, branching, or multiple destinations, use [Automations](/guides/automations/) instead. + + + + ## Tips - Leave the three health categories on. They stay quiet unless something is genuinely wrong. - A rising count of **Bounce** or **Spam complaint** entries is your cue to open [Deliverability](/guides/deliverability/) and find the campaign behind it. - Preferences are per user, so if a teammate is not seeing alerts you expect them to act on, check their own settings. + + ## Related guides diff --git a/docs/content/docs/guides/pipedrive.mdx b/docs/content/docs/guides/pipedrive.mdx index d21192280..693888f96 100644 --- a/docs/content/docs/guides/pipedrive.mdx +++ b/docs/content/docs/guides/pipedrive.mdx @@ -3,10 +3,58 @@ title: Pipedrive description: "Run your workspace's CRM on Pipedrive: deals, activities and notes written through, outreach logged on the person, and campaigns that stop when Pipedrive says someone has moved on." --- +Pipedrive mode makes Pipedrive your workspace's CRM. The deals, tasks and notes you see in Warmbly are Pipedrive's records, and your outreach is logged on each person. + +## Switch to Pipedrive + + + + + +Open **Integrations > Pipedrive** and select **Connect**. + + + + + +Sign in to Pipedrive, pick the company, and approve the permissions. + + + + + +In the setup wizard, select **Switch to Pipedrive**, then press **Looks good** through the rest. + + + + + +Finish the wizard. The dashboard fills in as the first pull runs, usually within a minute or two. + + + + + +## Good to know + +- Every change you make in Warmbly goes to Pipedrive first. If Pipedrive refuses it, nothing is saved. +- A deal opened in Pipedrive, a label you choose (such as **Customer**) or an unsubscribe holds the person's campaigns. +- Connect as a Pipedrive admin when you can. Without admin rights, the Warmbly fields are not written and Warmbly checks for changes every five minutes. +- Opens and clicks are not logged by default. +- Switching back or disconnecting deletes nothing on either side. + + + +## How Pipedrive mode works + A workspace can use Warmbly's own CRM, [HubSpot](/guides/hubspot/) or Pipedrive. In **Pipedrive mode**, Pipedrive is the system of record. The deals, pipelines, tasks and notes you see in Warmbly are Pipedrive's deals, pipelines, activities and notes, mirrored into Warmbly so lists, search and the board stay fast, and every change you make in Warmbly goes to Pipedrive first. Your outreach is logged as activities on each person, and Pipedrive can stop a campaign when a deal opens or a person is labeled a customer. The basic Pipedrive connection (a person upsert from an [automation](/guides/automations/#actions) or **Push to CRM**) works without Pipedrive mode. This page covers the full mode. + + + + ## What Pipedrive mode does - **Deals, pipelines, activities and notes are Pipedrive's.** A deal you create, move or edit in Warmbly is written to Pipedrive before it is saved here. Warmbly's tasks are Pipedrive activities. If Pipedrive refuses a change (a missing permission, a deleted record), you see Pipedrive's reason and nothing is saved. @@ -15,6 +63,10 @@ The basic Pipedrive connection (a person upsert from an [automation](/guides/aut - **Pipedrive can stop outreach.** A deal opened in Pipedrive, a label you choose (such as **Customer**) or an unsubscribe holds the person's campaigns. - **The contact panel shows Pipedrive's side.** In the contact drawer and the inbox, a contact shows their Pipedrive owner, label, organization and any fields you choose, and you can change the owner and label in place. + + + + ## Connect and set up 1. Open **Integrations > Pipedrive** and select **Connect**. Sign in to Pipedrive, pick the company, and approve the permissions. @@ -29,6 +81,10 @@ If the workspace runs on HubSpot when you switch, the wizard says so first. Swit Connect as a Pipedrive admin when you can. Pipedrive checks every change against the connected user's own permissions, and adding the Warmbly fields and the change notifications needs an admin (or the contact fields and webhooks permissions). Without them the sync still runs: fields are not written, and Warmbly checks Pipedrive every five minutes instead of hearing about changes at once. + + + + ## What syncs | Record | Direction | How | @@ -71,6 +127,10 @@ The Tasks page shows the Pipedrive activities owned by Pipedrive users who are m Creating, editing or deleting a task type is refused with `crm_managed_externally`, because the types are Pipedrive's activity types. Tasks Warmbly opens by itself, such as [follow-up tasks from replies](/guides/contacts-crm/#follow-up-tasks-from-replies) and an automation's **Create a task**, become Pipedrive activities as well. + + + + ## People and field mapping A Warmbly contact is linked to a Pipedrive person by email address. Linking happens when Warmbly emails the contact, when you open it, when a person whose address Warmbly has changes in Pipedrive, and after an [import from a Pipedrive filter](#importing-from-pipedrive-filters). @@ -89,6 +149,10 @@ The defaults map first name, last name, company and phone to the person's first You can also pick up to 40 of your Pipedrive fields to show on the contact panel. + + + + ## Activity logged to Pipedrive | In Warmbly | In Pipedrive | Default | @@ -105,6 +169,10 @@ Each activity is linked to the person, their organization and their open deal. I Opens and clicks are off by default because mail privacy proxies and security scanners open and click on their own. Turned on, they only update the two fields, at most once a day per person, and never create a person in Pipedrive. + + + + ## Warmbly fields Warmbly adds these custom fields to Pipedrive people when you switch, and keeps them up to date (on by default; turning it off stops every field write, including opens and clicks). A field your team already has with the same name is reused. @@ -120,6 +188,10 @@ Warmbly adds these custom fields to Pipedrive people when you switch, and keeps | Warmbly last clicked | Last click, when click logging is on | | Open in Warmbly | A link that opens the contact in Warmbly | + + + + ## Users Pipedrive's users are matched to workspace members by email address. Where the addresses differ, pick the member by hand on the Pipedrive page; a match you choose is kept and never replaced by the email match. Matching decides: @@ -128,6 +200,10 @@ Pipedrive's users are matched to workspace members by email address. Where the a - who owns the email activities a mailbox logs, and the people it creates - which Pipedrive user a deal or activity gets when you assign it to a member in Warmbly + + + + ## Positive replies When a reply is classified as interested, Warmbly can: @@ -138,6 +214,10 @@ When a reply is classified as interested, Warmbly can: The lead and the deal are both skipped when the person already has an open deal. The follow-up task comes from your existing [reply follow-up setting](/guides/contacts-crm/#follow-up-tasks-from-replies) and becomes a Pipedrive activity like any other task. + + + + ## Exit rules Exit rules hold a person in every campaign they are in when Pipedrive says they have moved on, so nobody gets a follow-up after becoming a deal or a customer. A held lead stays held until someone resumes it; the hold shows the reason, such as "A deal was opened in Pipedrive". @@ -159,6 +239,10 @@ Guards decide who an [import from a Pipedrive filter](#importing-from-pipedrive- | Skip people with an open deal | On | | Skip people owned by someone outside the workspace | Off | + + + + ## Working from inside Pipedrive The **Warmbly panel** on person and deal pages shows the person's Warmbly status, each campaign they are in with its step, status and last activity, and why a lead is paused, with a link that opens them in Warmbly. Its two actions work from Pipedrive itself: @@ -176,12 +260,20 @@ On a deal page the panel shows the deal's person. The panel acts as the Warmbly A Pipedrive user with no matched member, or whose member is still invited, is banned from signing in, or lacks the permission, sees why in the panel and changes nothing. The panel needs the Pipedrive company to be in Pipedrive mode in exactly one Warmbly workspace. On a self-hosted instance the panel is part of your own Pipedrive app; see [self-hosting](#self-hosting). + + + + ## Importing from Pipedrive filters From **Contacts**, choose **Import from Pipedrive** and pick one of your saved people filters. The preview counts who would come in and who is skipped, by reason: unsubscribed, label, open deal, owned by someone outside the workspace, no email address, and duplicate address. Continue, and the filter becomes an ordinary contact import, finished in the usual [import review](/guides/contacts-crm/#importing) where you pick the campaign, labels and duplicate handling. One import takes up to 25,000 people; a larger filter is cut off at that number and the preview says so. The imported contacts are linked to their Pipedrive persons a couple of minutes later, so their first sends log against the right person. + + + + ## Copying your Warmbly CRM into Pipedrive When a workspace that used Warmbly's CRM switches, its own deals, tasks and notes are not in Pipedrive yet. Records mirrored from a CRM the workspace used before (such as HubSpot) are not counted. The Pipedrive page counts them and copies the kinds you pick, once: @@ -191,6 +283,10 @@ When a workspace that used Warmbly's CRM switches, its own deals, tasks and note A record already in Pipedrive is never copied twice, so running it again only picks up what was missed. A Warmbly deal, task or note you edit after switching is also created in Pipedrive at that moment. + + + + ## Sync health and fixing errors The Pipedrive page shows what is waiting, what failed in the last runs, how many records are mirrored, and when each pull (users, pipelines, deals, activities, people), the change notifications and the Warmbly fields last ran with any error they hit. **Sync now** starts a full pull; a second one within a minute is refused with `crm_sync_running`. @@ -208,10 +304,18 @@ Changes you make in the dashboard go to Pipedrive while you wait. Activity loggi Warmbly keeps its calls well under Pipedrive's burst limit for each company (30 requests every two seconds and 4 searches, shared across every Warmbly process), and waits and retries when Pipedrive asks it to slow down. + + + + ## Switching back and disconnecting Choosing **Warmbly** as the CRM again, switching to HubSpot, or disconnecting Pipedrive stops the sync and the activity logging. Switching removes Warmbly's change notifications from Pipedrive; after a disconnect, Warmbly answers their deliveries as gone and Pipedrive retires them. Nothing is deleted on either side: the deals, pipelines, tasks and notes that were mirrored stay in Warmbly as ordinary records you can edit, the hidden Warmbly-only pipelines come back, and Pipedrive keeps everything that was written there, including the Warmbly fields. Switching back to the same Pipedrive company later picks up the same records rather than duplicating them. + + + + ## Self-hosting A self-hosted instance uses its own Pipedrive app. Create one in the Pipedrive Developer Hub, then: @@ -223,10 +327,18 @@ A self-hosted instance uses its own Pipedrive app. Create one in the Pipedrive D The consumer runs the pull and the sync queue. Change notifications are registered by Warmbly itself when a workspace switches, one set per connection, delivered to `/api/v1/integrations/pipedrive/webhooks/`. They need `BACKEND_PUBLIC_URL` to be an `https` address Pipedrive can reach; without one, Warmbly checks Pipedrive every five minutes instead. Pipedrive authenticates each delivery with a password Warmbly derives for that connection from the client secret, and a delivery only makes Warmbly read the named record sooner: it always reads the record from Pipedrive itself, and removes a mirrored record only when Pipedrive answers that it is gone. + + + + ## Moving a workspace A [workspace export](/guides/workspace-export-import/) carries the CRM mode, its setup choices, the user matches, which Warmbly record is which Pipedrive record, and each contact's Pipedrive details, so the destination keeps updating the same Pipedrive records instead of creating duplicates. The sync queue and the pull checkpoints stay behind; the destination starts its own pull and registers its own change notifications. Reconnect Pipedrive on the destination unless it uses the same Pipedrive app and the archive carried credentials. + + + + ## FAQ **Which Pipedrive plans work?** Every plan has the deals, people, activities, notes and filters Pipedrive mode uses. Unsubscribe status is only recorded on companies with Pipedrive Campaigns, so the unsubscribe exit rule and guard do nothing without it. @@ -241,6 +353,8 @@ A [workspace export](/guides/workspace-export-import/) carries the CRM mode, its **Does every member need a Pipedrive seat?** No. Members without a matched user still see deals, activities and notes; they cannot be assigned Pipedrive deals or activities, and their mailboxes log email under the person's owner. + + ## Next steps diff --git a/docs/content/docs/guides/placement-tests.mdx b/docs/content/docs/guides/placement-tests.mdx index b40d3132b..cbd011312 100644 --- a/docs/content/docs/guides/placement-tests.mdx +++ b/docs/content/docs/guides/placement-tests.mdx @@ -3,12 +3,66 @@ title: "Inbox placement tests" description: "Send a campaign step or a template from one of your mailboxes, or from a whole fleet at once, to seed inboxes at the major providers, and see where each copy landed." --- +A placement test sends a copy of your email from one of your real mailboxes to seed inboxes at different providers. It then shows where each copy landed: the primary inbox, a Gmail tab, spam, or nowhere at all. + +## Run a placement test + + + + + +Open **Placement tests** in the dashboard and start a new test. + + + + + +Choose a sending mailbox (connected, active, and not a seed inbox) and a seed panel. + + + + + +Pick a campaign step to test, or write a subject and body for an ad-hoc template. + + + + + +Choose a pace: **Spaced** (the default, around 20 minutes for 20 seeds) or **Quick** (under 3 minutes). + + + + + +Watch the results appear on the test as each seed syncs its copy. + + + + + +## Good to know + +- Every copy is a real send, charged to the sending mailbox's daily campaign limit. +- A copy not seen within 120 minutes of being sent is marked never arrived. +- On the hosted product, a paid plan gets `40` free tests a month on the shared panel by default, then `25` credits per test. A trial gets `3`. +- Tests on your own seed inboxes are never counted and never cost credits. +- One test is noise. Look for a pattern across several tests and providers. + + + +## What a placement test is and who can run one + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. A placement test answers one question before your prospects do: when this mailbox sends this email, where does it land? Warmbly sends a copy from a real mailbox of yours to a panel of seed inboxes spread across providers, then reads each seed's mail to see whether the copy reached the primary inbox, a Gmail tab, spam, or nowhere at all. Tests live under **Placement tests** in the dashboard. Reading results needs the **View analytics** permission, and starting or cancelling a test needs **Send campaigns**, because a test sends real mail from a real mailbox. + + + + ## What a test measures Every copy gets one of these verdicts: @@ -27,6 +81,10 @@ The rates are shares of the copies that were **delivered**, meaning every copy t Results are broken down by provider family: Gmail, Google Workspace, Outlook.com, Microsoft 365, Yahoo Mail, iCloud, Zoho and the rest. Gmail and Google Workspace are kept apart, as are Outlook.com and Microsoft 365, because the consumer and business services filter differently and an email can do well at one and badly at the other. + + + + ## How a test runs ### The copy is the one your campaign sends @@ -58,6 +116,10 @@ Seed inboxes are ordinary connected mailboxes that Warmbly syncs. As a seed's ma A copy not seen within 120 minutes of being sent is marked **never arrived**. Copies on Warmbly Cloud's panel get half an hour more, because their verdict travels back from the cloud. A test finishes when every copy has a verdict. A test in which no copy was delivered at all ends as **failed**, with the reason. + + + + ## Seed panels A test runs against one panel of seed inboxes: @@ -89,6 +151,10 @@ On **Your seed inboxes**, you can pick the seeds a test goes to instead of letti Every seed you choose gets a copy: the usual seeds-per-test cap does not apply to seeds you picked by hand. When the sending mailbox has too little of its daily limit left for all of them, the test is refused with the numbers rather than quietly skipping some. Seeds on the sender's domain are greyed out and skipped, and a seed that is not connected cannot be chosen. Choosing individual seeds is only possible on your own panel, because addresses on the shared and Warmbly Cloud panels are masked; there you pick providers instead. + + + + ## Your own seed inboxes Any mailbox connected to your workspace can become a seed inbox. Mark mailboxes on the **Seed inboxes** tab of the placement tests page; it needs the **Manage mailboxes** permission, and a workspace can have up to 50. @@ -107,6 +173,10 @@ Marking a mailbox as a seed changes how Warmbly treats it: A mailbox on the instance panel cannot also be one of your seeds, and a mailbox cannot be marked while a test is still sending from it. Unmarking a seed makes it an ordinary mailbox again; its warmup stays off until you start it. + + + + ## Starting a test A test needs a sending mailbox, a panel, and a copy to send. The sending mailbox has to be connected and active, and cannot itself be a seed inbox. @@ -127,6 +197,10 @@ Cancelling a running test stops the copies that have not been sent yet. Copies a A comparison sends each seed both versions back to back, in a random order per seed, so both see the same filter at the same moment. The difference between the two halves is what the pixel and the wrapped links cost you. Both halves send a seed the same words: the copy for that seed is rendered once, with its spintax, merge fields, AI blocks, form links, signature and opt-out line resolved, and the tracked half only adds the pixel and wraps the links. Different seeds can still draw different spintax, as different leads would. If a seed's copy cannot be rendered, neither half is sent to that seed. It counts as two tests and sends twice as many copies. A campaign that sends plain text carries no tracking, so **On** and **Compare** are refused for it (`placement_invalid_tracking`). + + + + ## What a test costs **Every copy is a send from the sending mailbox.** It is charged to the mailbox's daily campaign limit, and the mailbox's campaigns see those sends in their daily budget and in their minimum gap between sends. A test to 20 seeds uses 20 sends of that day; a comparison uses 40. Copies still waiting to go out already hold their place in the day, so a campaign cannot spend them while the test sends. When the mailbox has less left than a full test needs, the test goes to fewer seeds instead; below five seeds per test it is refused with `placement_daily_budget`, because a smaller test is noise. Test from a mailbox with headroom, or before its campaigns run for the day. @@ -153,6 +227,10 @@ These are the defaults, and the operator can change both numbers. The month is t Tests on your own seed inboxes are never counted and never cost credits. A self-hosted instance does not meter its own panel at all. Tests on Warmbly Cloud's panel use the free tests of the Warmbly Cloud workspace the instance is linked to; paying for more with credits is not offered there. + + + + ## Campaign placement monitors A monitor re-tests a running campaign on a schedule, so a drop in placement shows up before it shows up in your reply rate. Set it up from the campaign. @@ -170,6 +248,10 @@ A monitor only tests a campaign that is running. When a run cannot start (the ca **When a monitor's test lands below its threshold**, everyone in the workspace who can view campaigns gets a **Placement monitor alert**, with the result and a link to the test. With **Pause on alert** on, the campaign is also paused automatically, with the reason in its activity log. Like every [auto-pause](/guides/campaigns/#auto-pause-guardrails), it does not resume on its own: find out what changed, then start the campaign again yourself. + + + + ## Testing a fleet with batches A test answers where one mailbox lands. With hundreds or thousands of sending mailboxes the question is different: is the fleet healthy, and if not, is the problem one mailbox, one domain, or one provider? A **placement batch** runs the same test from many mailboxes and reads the results together. Start one with **New batch** on the placement tests page, with **Test sender pool** on a campaign's settings, or with **Test untested mailboxes** from the coverage line. @@ -242,6 +324,10 @@ Every mailbox's test counts as one test against the monthly free tests (two for The placement tests page shows how much of your connected fleet finished a placement test in the last 30 days, and how many mailboxes never did. **Test untested mailboxes** starts a batch of exactly those. + + + + ## Reading the results honestly A placement test is a signal, not a verdict. Read it with its limits in mind: @@ -254,6 +340,10 @@ A placement test is a signal, not a verdict. Read it with its limits in mind: Tests also feed the seed placement figures on the [Deliverability](/guides/deliverability/#the-dashboard) page, which roll every test in the window up by provider. + + + + ## Notifications | Notification | Who gets it | Default | @@ -263,6 +353,10 @@ Tests also feed the seed placement figures on the [Deliverability](/guides/deliv Both open the test, or the batch. Change them under **Settings > Notifications**; see [notifications](/guides/notifications/). + + + + ## Limits | Limit | Value | @@ -280,10 +374,16 @@ Both open the test, or the batch. Change them under **Settings > Notifications** | Time for a copy to arrive | `120` minutes after it was sent | | Monitor interval | `1` to `30` days | + + + + ## Moving a workspace Placement tests and their results travel in a [workspace export](/guides/workspace-export-import/) as a record, under Delivery events (without what a paid test cost, since credits do not travel), and so do placement batches with their mailboxes and results. A batch still running when it is exported arrives cancelled, so the destination never starts sending it. Campaign placement monitors travel with the campaigns. Which mailboxes are seed inboxes does not travel, so mark them again on the destination. A test that is still sending leaves its unsent copies behind. + + ## Related guides diff --git a/docs/content/docs/guides/referral-program.mdx b/docs/content/docs/guides/referral-program.mdx index 3954346e3..5e4b78c9a 100644 --- a/docs/content/docs/guides/referral-program.mdx +++ b/docs/content/docs/guides/referral-program.mdx @@ -3,6 +3,44 @@ title: Referral program description: Share Warmbly, give friends a discount, and earn account credit when they upgrade. --- +Share your referral link and the teams you refer get a discount. When they upgrade to a paid plan, you earn account credit that comes off your future invoices. + +## Refer a team + + + + + +Open **Settings, Referral** (it needs the **Manage billing** permission). + + + + + +Copy your ready-to-share link. + + + + + +Send it to anyone you want to refer. The code is captured automatically when they sign up. + + + + + +## Good to know + +- People you refer get **10% off their first 3 months**, applied automatically at checkout. +- You earn one month-equivalent of their plan price, once per referred organization, and rewards stack. +- Credit comes off your next invoices automatically. There is nothing to redeem. +- A reward is reversed if the invitee refunds or cancels within 30 days of it being granted. +- Self-referrals are blocked, and there is a monthly cap on rewarded conversions per referrer. + + + +## What the program is and who can manage it + The referral program rewards you for bringing other teams to Warmbly. You share a personal code, the people you refer get a discount on their first few months, and when they upgrade to a paid plan you earn account credit that comes straight off your future invoices. This guide covers where to find your code, what an invitee gets, what you earn, how the credit is applied, the 30-day clawback, the fraud and limit rules, and a short FAQ. @@ -11,6 +49,10 @@ This guide covers where to find your code, what an invitee gets, what you earn, The referral page lives under **Settings, Referral** and requires the **Manage billing** permission, the same permission that controls the rest of billing. The owner and any role granted billing access can open it; other members do not see it. + + + + ## Finding your code Open **Settings, Referral** in the dashboard. Warmbly mints one referral code per workspace the first time you visit, and the page shows it along with a ready-to-share link in the form: @@ -25,12 +67,20 @@ On a trusted secondary dashboard sharing the backend, the share link uses that d The same page shows your running totals: how many people have signed up with your code, how many converted to a paid plan, and how much credit you have earned so far. + + + + ## What the invitee gets Anyone who signs up through your link gets **10% off their first 3 months**. It is a repeating discount that is applied automatically at checkout, so the invitee does not need to enter a promo code. After the third month the discount ends and they pay the standard plan price. The discount applies to whichever paid plan the invitee chooses when they upgrade. It does not require them to start on a specific plan. + + + + ## What you earn When an invitee converts to a paid plan, you earn **account credit equal to one month-equivalent of their plan price**: @@ -45,18 +95,30 @@ Annual plans count as a single month-equivalent of the plan price, not the whole You earn **one reward per referred organization**, and rewards **stack across referrals**: refer five teams that all upgrade to Pro and you earn five times the Pro reward. + + + + ## How credit is applied Credit is applied automatically. When a referred organization converts, Warmbly adds the reward to your Stripe customer balance, and that balance nets off your future Warmbly invoices. You do not need to redeem anything or contact support: the next invoice simply draws down the available credit first, and any remainder rolls forward to later invoices. Because the credit lives on your billing account, you can see it reflected in the billing portal alongside your normal invoices. + + + + ## The 30-day clawback Rewards are not final the instant they are granted. Each reward has a **30-day clawback window**: if the invitee refunds or cancels their subscription within 30 days of the reward being granted, the credit is reversed and removed from your balance. This keeps the program honest and means earned credit settles once the referred subscription has held for the window. After 30 days the reward is no longer subject to clawback. + + + + ## Limits and fraud rules To keep the program fair, a few rules apply automatically: @@ -69,6 +131,10 @@ To keep the program fair, a few rules apply automatically: Credit is provisional until the clawback window closes. Refunds, cancellations inside the window, and conversions that breach the rules above will not result in lasting credit. + + + + ## Frequently asked questions ### Does the invitee need to enter a code? @@ -95,6 +161,8 @@ As many as you like, and rewards stack. There is a per-referrer monthly cap on r On your Stripe customer balance, which automatically reduces your next Warmbly invoices. You can see it in the billing portal. + + ## See also - [Team and roles](/guides/team-roles/) for the billing permission that controls access to the referral page. diff --git a/docs/content/docs/guides/salesforce.mdx b/docs/content/docs/guides/salesforce.mdx index 1a7c51bda..72f45ed15 100644 --- a/docs/content/docs/guides/salesforce.mdx +++ b/docs/content/docs/guides/salesforce.mdx @@ -3,8 +3,56 @@ title: Salesforce description: "Two-way Salesforce sync: activity on the timeline, Lead Status writeback, do-not-email in step, imports from list views and Campaigns, and Salesforce context on every contact." --- +The Salesforce integration keeps your Salesforce org and Warmbly in step. Campaign emails, replies and meetings are logged on the Lead or Contact, and an opt-out on either side stops email on both. + +## Connect Salesforce + + + + + +Open **Integrations > Salesforce > Connect**. + + + + + +Choose where your org lives: **Production**, **Sandbox**, or **Custom domain**. + + + + + +Sign in to Salesforce and approve access. + + + + + +On the connection's page, use **Overview > Run permission check** to see anything missing. + + + + + +## Good to know + +- Your Salesforce edition needs API access. Essentials has no API. +- A new connection logs sends, replies, bounces, unsubscribes and meetings, never overwrites a filled field, and only creates a Lead when someone replies. +- Opens and clicks are not logged by default. +- Warmbly reads what changed in your org every five minutes. +- By default Warmbly uses at most a fifth of your org's daily API allocation. + + + +## What the integration does + The Salesforce integration keeps your org and Warmbly in step without anyone copying data between them. Campaign emails, replies and meetings land on the Lead or Contact timeline as completed Tasks, a reply can move the Lead Status, an opt-out on either side stops email on both, and every contact drawer and inbox thread shows who owns the person in Salesforce and which deals are open. + + + + ## What you need - A Salesforce edition with API access: Enterprise, Unlimited, Performance or Developer, or Professional with the API add-on. Essentials has no API. @@ -13,6 +61,10 @@ The Salesforce integration keeps your org and Warmbly in step without anyone cop Warmbly acts as the connecting user. Records it creates and Tasks it logs are owned according to the rules below, but every call is made with that user's access, so a dedicated integration user with the permissions above is the cleanest setup. + + + + ## Connect 1. Open **Integrations > Salesforce > Connect**. @@ -27,6 +79,10 @@ Connecting a sandbox and production at the same time is supported; each is its o **Overview > Run permission check** describes Leads, Contacts and Tasks as the connected user and says exactly what is missing, for example "Email Opt Out is not editable for this user, so unsubscribes cannot be written back". + + + + ## How people are matched Every Warmbly contact is linked to at most one record per connected org, by email address: @@ -39,6 +95,10 @@ A created record gets the contact's name, company (the email domain when there i Links are made as activity flows, when a contact's drawer opens, on import, and from **Push to Salesforce** in Contacts. + + + + ## Activity on the timeline Each event is logged as a completed Task on the Lead or Contact, with the email icon in the timeline: @@ -59,6 +119,10 @@ A Contact's Tasks are related to the account's most recently active open opportu Every event is recorded the moment it happens and logged in batches of up to 200, normally within a minute. A send is logged once it has actually gone out, so a send that failed and was retried appears once. Each event is logged once even if it is reported twice. Turn **Include the email text** off to log subjects and metadata only. + + + + ## Status writeback On a Lead (never a converted one), Warmbly can move Lead Status: @@ -69,6 +133,10 @@ On a Lead (never a converted one), Warmbly can move Lead Status: With **Never move backwards** on, a Lead already further along the status picklist stays where it is. + + + + ## Do not email, both ways With **Opt-out sync** set to both (the default): @@ -78,6 +146,10 @@ With **Opt-out sync** set to both (the default): You can restrict it to one direction or turn it off. + + + + ## When Salesforce changes Warmbly reads what changed in your org every five minutes and refreshes linked contacts: owner, Lead Status, account, conversion and opt-out. It can also pause outreach, the same hold a member sets on a lead, in every campaign the contact is in: @@ -88,6 +160,10 @@ Warmbly reads what changed in your org every five minutes and refreshes linked c A paused lead shows "Paused by Salesforce" with the reason in its campaign drawer and stays paused until someone resumes it. A rule never replaces a pause a member set, and each change triggers it once, so resuming a lead is not undone by the next pull. + + + + ## Field mapping **Field mapping** lists one rule per field, per object: @@ -101,6 +177,10 @@ Warmbly fields are first name, last name, company, phone, any contact custom fie "Only fill blanks" never writes a field Warmbly has not read, so it cannot overwrite a value it never saw. The default rules fill names, phone and company in both directions without overwriting either side. + + + + ## Import from Salesforce **Import > New import** brings people in from: @@ -112,10 +192,18 @@ Preview shows the count and a sample before anything is written. Choose a Warmbl Turn on **Keep in sync** and the import re-reads the list every 30 minutes, bringing in only people who are new to it, so a teammate's edits in Warmbly are never overwritten by the next run. One run imports up to 10,000 people; the rest come in on the following runs. + + + + ## Contacts and the inbox The contact drawer and every inbox thread show a Salesforce card: the Lead or Contact, owner, Lead Status, account, open opportunities, the latest Salesforce Tasks (Warmbly's own marked), opt-out and conversion badges, and when it last synced. **Open in Salesforce** goes straight to the record. When the person is not in Salesforce yet, **Add to Salesforce** creates them as a Lead or Contact. **Sync now** refreshes the card and pushes mapped fields. + + + + ## Sync health - **Overview** shows the connection's health, Warmbly's API calls today against its budget, the org's own daily usage, pending and failed activity, and the last pull. @@ -130,14 +218,26 @@ Warmbly caps its own daily calls, a fifth of your org's daily allocation by defa When Salesforce ends the session (a password reset, a revoked app, a deactivated user), the connection shows **Reconnect** and activity waits. Nothing is lost; it is logged after you reconnect. + + + + ## Automations The **Create or update Salesforce record** action in [automations](/guides/automations/) uses the same matching, creation and field rules as the sync, so an automation and the sync never disagree about who someone is. + + + + ## What moves with a workspace export Saved imports travel with the Automations group of a [workspace export](/guides/workspace-export-import/). Links between contacts and records, the activity log and pull progress do not: the destination links contacts again by address the first time it syncs, and Tasks already logged are in Salesforce. Reconnect Salesforce on the destination before anything syncs. + + + + ## Self-hosting A self-hosted instance needs its own Salesforce app. Which kind depends on how many Salesforce orgs will connect: @@ -153,3 +253,5 @@ Whichever you create, configure it with: - refresh tokens **valid until revoked**. Copy the consumer key and secret into `SALESFORCE_OAUTH_CLIENT_ID` and `SALESFORCE_OAUTH_CLIENT_SECRET` (see [configuration](/development/configuration/#integrations)) and restart the backend. Salesforce can take a few minutes to activate a new app; until then a connect attempt fails with `invalid_client_id`. + + diff --git a/docs/content/docs/guides/security.mdx b/docs/content/docs/guides/security.mdx index 095f43e3a..bcd796808 100644 --- a/docs/content/docs/guides/security.mdx +++ b/docs/content/docs/guides/security.mdx @@ -3,8 +3,56 @@ title: "Security" description: "Two-factor authentication, passkeys, and sessions." --- +**Settings > Security** is where you protect how you sign in to Warmbly: two-factor authentication, passkeys and your signed-in devices. + +## Secure your account + + + + + +Open **Settings > Security**. + + + + + +Choose **Add a passkey** so daily sign-in is fast and phishing-resistant. + + + + + +Turn on two-factor authentication with any standard authenticator app, and store the recovery codes safely. + + + + + +Review **Sessions** now and then, and sign out anything unfamiliar. + + + + + +## Good to know + +- Recovery codes are shown only once, and each one works once. +- A passkey skips the emailed code, because unlocking your device already proves it is you. +- Changing your password ends every session. The device you are on is signed straight back in. +- Some sensitive changes ask for your password or an authenticator code again. The confirmation lasts five minutes. +- A password has to be 8 to 128 characters, and commonly breached passwords are refused. + + + +## Where these settings live + Everything here lives under **Settings > Security** and concerns signing in to Warmbly. For authenticating the domains you send from (SPF, DKIM, DMARC), see [Deliverability](/guides/deliverability/). + + + + ## How signing in works | Method | Steps | @@ -23,6 +71,10 @@ Accounts created through an external sign-in provider have no password initially A passkey is tied to your device and unlocked with biometrics or a PIN, so it already proves both something you have and something you are or know. That is why it skips the emailed code. Warmbly requires that unlock: a security key with no PIN set cannot be added, and cannot sign in, until you set one on the key. + + + + ## Two-factor authentication 2FA (TOTP) adds a rotating 6-digit code to every password sign-in, so a stolen password alone is not enough. Any standard authenticator works: Google Authenticator, Microsoft Authenticator, 1Password, Authy, Bitwarden. @@ -47,6 +99,10 @@ Wrong codes are limited three ways: five per sign-in attempt, after which you si **Turn off** asks for a current code or a recovery code first, then an explicit confirmation. With 2FA off you are protected only by your password plus the emailed code, your recovery codes stop working, and setting it up again issues a fresh secret and new recovery codes. + + + + ## Passkeys A passkey signs you in with whatever unlocks your device. Nothing is phishable, because the credential never leaves the device. Current Chrome, Safari, Edge, and Firefox all support them; if yours does not, the Security page says so and you keep using your password. @@ -61,6 +117,10 @@ Each entry shows when it was added and last used, and can be renamed or removed. Unsynced passkeys live only on the device that created them. On a device without one, Warmbly says none was found and you sign in with your password, then add a passkey there. + + + + ## Confirming sensitive changes Some changes ask you to confirm it is you even though you are already signed in: @@ -81,6 +141,10 @@ If you signed up with Google, Apple or your company's single sign-on, your accou This exists because the things on that list either hand out a credential that outlives the session that made it, reveal a secret, change who can get in, or cannot be undone from your side. Someone who got hold of a signed-in browser should not be able to do any of them without knowing something you know. + + + + ## Sessions Every signed-in device appears under **Sessions** with its device (`Chrome on macOS`), location where known, sign-in method (Email, Google, Apple, or Passkey), and last activity. Your current device is tagged **This device**. @@ -93,6 +157,10 @@ A session renews itself in the background, and each renewal replaces the credent Sign out the session, change your password, and make sure 2FA is on. Signing out other sessions is the fastest way to cut off access. + + + + ## Choosing a password A password has to be between 8 and 128 characters, and it is checked against a list of the 100,000 most commonly breached passwords published by the UK National Cyber Security Centre. If yours appears there, it is refused and you are told why. The check is case-insensitive, so capitalising the first letter of a known password does not get past it. @@ -101,16 +169,28 @@ There is no rule about mixing upper case, digits and symbols. A long passphrase Repeated wrong passwords are counted per account, not just per device, so guessing one account from many addresses does not buy an attacker more attempts. After fifty failures in an hour the account stops accepting password attempts until the hour is up. Signing in correctly or completing a password reset clears the count. The current password asked for when you change it, and the password you confirm a sensitive change with, share a separate budget of the same size. + + + + ## Names Your name and your workspace's name appear in emails Warmbly sends to other people, such as invitations. They are plain text only: a name that contains a link, a web address, an email address or invisible formatting characters is refused, and the field tells you why. The full rules are in [Name refusals](/api/error-codes/#name-refusals). + + + + ## Password and alerts Change your password under **Password**: current password, then a new one. **Changing it ends every session, including the one you change it from.** The device you are on is signed straight back in with a new session, so you notice nothing, while anything that held your old session, on any device, is signed out. Accounts that only use Google, Apple, or a passkey have no password to change. **Sign-in alerts** notify you when your account is accessed from a new browser and OS combination, naming the device and location with a reminder to act if it was not you. They appear in your in-app feed by default; enable email under **Settings > Notifications**, Security section, Email channel. Your first sign-in is never alerted, as there is nothing to compare against. + + + + ## What is recorded when you sign up Creating an account records the address it was created from, the browser's user-agent string, and a score for how the email address itself reads. This is anti-abuse evidence: it is what lets the platform notice one person opening many accounts, or a wave of signups from one place, without which every account looks unrelated to every other. @@ -121,6 +201,10 @@ Creating an account records the address it was created from, the browser's user- Nothing here refuses a signup on its own. A single weak signal is wrong too often, and turning a false positive into a rejected account leaves a real customer with no way through. + + + + ## Sign-ins from an impossible place A hijacked workspace owns warmed mailboxes and decryptable sending credentials, so it is worth more to an attacker than an ordinary account. Sign-in alerts tell you after the fact; this stops the sign-in. @@ -141,6 +225,10 @@ Repeated anomalies inside a fortnight are recorded against the workspace's postu This uses the optional MaxMind city database. Without it configured, sign-ins are recorded but never judged, which is the safe direction: a false challenge locks a real person out of their own account. + + + + ## Cross-account patterns Individually, every abuse control watches one thing: a rate limit watches one account, warmup health one mailbox, verification one address. Someone spreading the same behaviour across several accounts stays under all of them. @@ -154,12 +242,18 @@ Findings are recorded as evidence on the [workspace posture](/guides/deliverabil - **Sharing an address carries little weight** on its own. Offices, co-working spaces and VPNs all produce it honestly. - **Only the last 30 days count.** Two workspaces opened from one office a year apart are not related. + + + + ## Strongest setup 1. **Add a passkey** so daily sign-in is fast and phishing-resistant. 2. **Turn on 2FA** and store the recovery codes safely, so password sign-ins keep a second factor. 3. **Review sessions** periodically and sign out anything unfamiliar. + + ## Related guides diff --git a/docs/content/docs/guides/segments.mdx b/docs/content/docs/guides/segments.mdx index c3a11478e..26071aeee 100644 --- a/docs/content/docs/guides/segments.mdx +++ b/docs/content/docs/guides/segments.mdx @@ -3,12 +3,60 @@ title: Segments description: Save reusable audiences built from contact fields, labels, campaign activity and email engagement, then add them to campaigns in one step. --- +A segment is a saved audience: a set of conditions over your contacts, plus any contacts you pin in or out by hand. Membership is evaluated live, so a contact who starts matching is in the segment with no rebuild step. + +## Build a segment + + + + + +Open **Contacts > Segments** and choose **New segment**, or set filters on the contact list and press **Save as segment**. + + + + + +Give the segment a name and a color. + + + + + +Add conditions, and pick whether a contact must match **all** of them or **any**. + + + + + +Watch the live count of matching contacts, then save. + + + + + +## Good to know + +- Browsing needs **View contacts**; creating, editing or deleting needs **Manage contacts**. +- **Add to campaign** copies today's members once; linking a segment to a campaign keeps enrolling new members. +- A segment with no conditions is a manual list of the contacts you pin in. +- A workspace can have up to 200 segments, each with up to 50 conditions. +- A segment linked to a campaign cannot be deleted until you detach it. + + + +## What a segment is and where it lives + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. A segment is a saved audience: a set of conditions over your contacts plus any contacts you pin in or out by hand. A segment says which contacts belong together; a campaign says what happens to them. Membership is evaluated live, so a contact that starts matching (a new label, a reply, a bounce) is in the segment the next time anyone looks, with no rebuild step. Segments live under **Contacts > Segments**, next to **All contacts** and **Labels**, and need the same permissions as contacts: **View contacts** to browse, **Manage contacts** to create, edit or delete. + + + + ## Building a segment The quickest start is from the contact list: set any filters in the bar above the list, then press **Save as segment** and the editor opens with those filters as conditions (free-text search and a campaign's lead status do not carry over; add a condition for them instead). Or start from scratch with **New segment** on the Segments tab. @@ -37,6 +85,10 @@ A segment can be built on other segments (**In segment**), up to five levels dee A segment with no conditions is a manual list: it holds only the contacts you add by hand. + + + + ## Adding and removing contacts by hand Conditions decide membership, and two overrides sit on top of them: @@ -52,6 +104,10 @@ Sequences can pin too: the **Add to segment** and **Remove from segment** action A segment with no conditions at all is a plain list: it holds exactly the contacts you pin in, so creating one and importing into it is the shortest way to turn a file into an audience. + + + + ## Using a segment - **Browse**: a segment page lists its current members with the same table, filters, detail drawer and bulk actions as the contacts page, including [**Select all matching**](/guides/contacts-crm/#selecting-rows) so an action covers every member and not only the rows loaded so far, for selections up to `250,000` contacts. @@ -63,6 +119,10 @@ A segment with no conditions at all is a plain list: it holds exactly the contac - **Duplicate**: the segment menu copies a definition to start a variation from. - **Search and export**: the contact search and export accept `segment_ids`, so anything that takes a contact filter can be scoped to a segment. + + + + ## Linking a segment to a campaign Where **Add to campaign** copies today's members once, a linked segment is a live audience: the campaign keeps enrolling whoever the segment says belongs. Manage the links from the **Segments** button on a campaign's Leads tab; a campaign can link up to 20 segments. @@ -89,6 +149,10 @@ The campaign reacts to growth by status: A segment linked to a campaign cannot be deleted; detach it from the campaign first. + + + + ## Segments in the API Everything above can be driven from the [API](/api/reference/contacts/#segments): list, create, update and delete segments, pin contacts in or out, look up a contact's segments, enrol a segment into a campaign once, and [link segments to a campaign](/api/reference/campaigns/#list-linked-segments) as a live audience. Contact search and export take `segment_ids` to scope any contact query to a segment, so an external system (a signup form, a CRM sync) can keep a segment current and let campaigns pick it up from there. @@ -99,12 +163,18 @@ API calls address a segment by its ID. It is shown at the bottom of the segment Labels are something you put on a contact. Segments are rules that read those labels (and everything else) to decide who belongs. Use a label to mark a fact about a contact, and a segment to describe an audience. + + + + ## Limits - 200 segments per workspace - 50 conditions per segment, 200 values per list condition - 10,000 ticked contacts, or `250,000` through **Select all matching**, per manual add or remove + + ## Where to go next diff --git a/docs/content/docs/guides/sending-behavior.mdx b/docs/content/docs/guides/sending-behavior.mdx index 4a1c20d48..28128e4d0 100644 --- a/docs/content/docs/guides/sending-behavior.mdx +++ b/docs/content/docs/guides/sending-behavior.mdx @@ -3,12 +3,60 @@ title: "Sending behavior" description: "Give each mailbox a human workday: randomized hours, volume, lunch break, and spacing, in its own timezone." --- +Sending behavior gives each mailbox a human workday, so its sending does not look automated. You set ranges instead of fixed values, and each day the mailbox rolls its own start, finish, lunch break, daily target and hourly ceiling, in its own timezone. + +## Set up a mailbox's workday + + + + + +Open **Mailboxes** and pick a mailbox. + + + + + +Open the **Sending** tab and turn on **Variable sending plan**. + + + + + +Keep the default ranges, or adjust them. + + + + + +Check today's rolled workday at the top of the tab to see when the mailbox will send. + + + + + +## Good to know + +- New mailboxes start with it on and a daily target of `28` to `32` cold emails, about `30`. +- It can only lower volume or delay a send. It never raises a mailbox above its own daily cap. +- Times are local to the mailbox's timezone, or the workspace timezone when it has none. +- Editing the ranges does not change today. The new ranges apply from the next roll. +- Warmup keeps the same working days and hours, but does not count against the cold limits. + + + +## Why sending gets a workday + Automated sending looks automated. A mailbox that sends exactly 40 emails every day, starts at 09:00:00, and spaces every send exactly 600 seconds apart is describing itself as a machine in its own headers. Sending behavior fixes that. You give a mailbox ranges instead of fixed values, and it rolls one workday out of those ranges each day: a start time, a finish time, a lunch break, a daily target, an hourly ceiling. It then keeps to that day. Everything here is per mailbox and lives in its own timezone, so a London mailbox and a Denver mailbox on the same campaign each keep their own office hours. + + + + ## Turn it on **Mailboxes** > pick a mailbox > **Sending** > **Variable sending plan**. @@ -17,6 +65,10 @@ Newly connected mailboxes start with this enabled and a configurable daily targe While it is off, the rest of the tab is greyed out and read-only, because none of it applies. Turn the switch on to edit the ranges. + + + + ## What gets randomized | Setting | Default range | What it controls | @@ -33,6 +85,10 @@ Every time is local to the mailbox's timezone: its own, set on the **Settings** A new mailbox has no timezone of its own, and that is a valid state rather than a gap: its workday and warmup hours are read in the workspace timezone (UTC when that is not set either), and on campaigns only the campaign's own sending window applies. Set one when the mailbox belongs to somebody in a particular place, and both calendars then have to agree before a send goes out. + + + + ## The daily plan The roll happens once per mailbox per local day, and the result is stored. That matters: the scheduler recomputes a mailbox's next slot many times a day, and a fresh random workday on every calculation would mean the mailbox never actually keeps to one. @@ -43,6 +99,10 @@ Emails reserved for dispatch count once against the day's target; a cancelled re Editing the ranges does not change today. Today keeps the workday it already started, and the new ranges take effect on the next roll. + + + + ## How it interacts with your other limits Sending behavior can only ever **lower** volume or **delay** a send. It can never raise a mailbox above its own daily cap or bring a send forward. @@ -57,6 +117,10 @@ Sending behavior can only ever **lower** volume or **delay** a send. It can neve Without it, a mailbox with a 40/day target and a 90-second floor could empty its whole day before 11am and then sit silent. That is a more obvious pattern than sending too much. + + + + ## Choosing ranges The defaults describe an ordinary weekday sender and are a good starting point for most mailboxes. If you change them: @@ -68,6 +132,10 @@ The defaults describe an ordinary weekday sender and are a good starting point f The break has to fit inside the shortest workday the ranges can produce, and the latest possible start has to come before the earliest possible finish. The editor blocks a save that would break either rule. + + + + ## Sending in the recipient's timezone Sending behavior places a send inside the **mailbox's** day. Send-time optimization places it inside the **recipient's** day: hold each campaign email until the clock where the recipient reads mail reaches an hour you chose. @@ -94,6 +162,10 @@ Send-time optimization can move a send later, never earlier. Every other constra Because it delays sends, a campaign with tight delivery hours and a small mailbox pool sends fewer emails per day than one without. Widen the hours if throughput matters more than placement. + + + + ## API The profile and today's plan are both readable and writable through the API. See [Endpoints](/api/endpoints/) for the scopes. @@ -105,3 +177,5 @@ GET /v1/emails/{id}/behavior/plan ``` `PUT` is a partial update: send only the fields you want to change. It is naturally idempotent, so retries are safe without an `Idempotency-Key`. + + diff --git a/docs/content/docs/guides/sending-domains.mdx b/docs/content/docs/guides/sending-domains.mdx index ca9474b12..64b27d1dc 100644 --- a/docs/content/docs/guides/sending-domains.mdx +++ b/docs/content/docs/guides/sending-domains.mdx @@ -3,12 +3,62 @@ title: Sending domains description: "Every domain your mailboxes send from, with its authentication, tracking host and a redirect for visitors to the bare domain." --- +A sending domain is any domain one of your mailboxes has an address on. Warmbly lists them for you, and from here you give each one a tracking host and a redirect for visitors to the bare domain. + +![Sending domains: each domain with its mailboxes, authentication and tracking host](/guides/sending-domains.webp) + +## Set up your domains + + + + + +Tick the domains in the list, or the box in the header for every domain shown. + + + + + +In the bar at the bottom, choose **Tracking domain** or **Redirect**. + + + + + +Set the tracking subdomain (`link` unless you change it) and the website to send visitors to. + + + + + +Add the DNS records the dialog lists at your DNS provider, unless an inbox vendor sets them up for you. + + + + + +## Good to know + +- There is nothing to add: connect a mailbox on `acme.io` and `acme.io` appears here. +- A tracking host is saved before its DNS record exists. Links keep using the shared tracking host until the record resolves. +- A redirect sends visitors to the bare domain on to your company's website, so the domain looks like part of a real business. +- Mail is unaffected: `MX`, SPF, DKIM and DMARC records live beside these and nothing here changes them. +- When an inbox vendor holds the domain, Warmbly can forward it or write the tracking record there for you. + + + +## What a sending domain is + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. A sending domain is any domain one of the workspace's mailboxes has an address on. Warmbly builds the list from the mailboxes themselves, so there is nothing to add: connect a mailbox on `acme.io` and `acme.io` is here. The same choices are offered per domain while [importing mailboxes](/guides/mailbox-import/#tracking-domains-and-root-redirects), so a new domain can be set up in one pass. + + + + ## The domains list Each domain shows: @@ -21,6 +71,10 @@ Each domain shows: | Redirect | Where visitors to the bare domain are sent, and whether the redirect is live | | Vendor | The [inbox vendor](/guides/mailbox-import/#import-from-an-inbox-vendor) account that holds the domain, when one of the workspace's connected vendor accounts does, with its current forwarding and what its API can do for the domain | + + + + ## One tracking host for a whole domain A tracking host is normally set per mailbox. From the domain you can set one for every mailbox on it in one step, or clear it for all of them. @@ -31,6 +85,10 @@ The host is saved even before its DNS record exists. Until the record resolves, When an inbox vendor account holds the domain and can write DNS records for it, Warmbly can add the `CNAME` for you instead; see [domains held by an inbox vendor](#domains-held-by-an-inbox-vendor). + + + + ## Set up many domains at once Tick domains in the list (or the box in the header for every domain shown; hold Shift to tick every row between two clicks) and a bar appears at the bottom: @@ -56,6 +114,10 @@ The dialog says up front how many domains an [inbox vendor](#domains-held-by-an- While any domain still uses the shared tracking host or has no redirect, the top of the page says how many and offers **Set up all**, which opens the same dialog for exactly those domains. Dismissing it hides it until another domain needs setting up. + + + + ## Root redirects A domain bought for cold email usually answers nothing when someone types it into a browser. A recipient who checks who wrote to them, or a filter that looks at the domain, finds an empty page or an error, which reads as a throwaway domain. A redirect sends visitors to the bare domain (and its `www`) on to your company's main website instead, so the domain looks like what it is: part of a real business. @@ -143,6 +205,10 @@ On a self-hosted instance [linked to Warmbly Cloud](/guides/warmbly-cloud/), a r - The instance needs a tracking host (`TRACKING_DOMAIN`), because the tracking service is what answers. Without one, redirects are not offered, except from Warmbly Cloud on a linked instance. - Warmbly Cloud serves up to 200 redirects for one linked instance. + + + + ## Domains held by an inbox vendor A domain bought from an [inbox vendor](/guides/mailbox-import/#import-from-an-inbox-vendor) usually has its DNS hosted by that vendor, and most vendors can forward the domain themselves. When one of the workspace's connected vendor accounts holds a domain, the domain shows that vendor, and two shortcuts replace the DNS work above: @@ -168,10 +234,16 @@ A [mailbox import](/guides/mailbox-import/#tracking-domains-and-root-redirects) The vendor's domain list is read with the workspace's own API key, only for connections that are active, and kept for five minutes. A vendor that does not answer is left out, so its domains show without a vendor until it does. + + + + ## What travels in an export Redirects travel in a [workspace export](/guides/workspace-export-import/). They arrive unverified and are served once the destination's own check sees both records. One Warmbly Cloud served for the source arrives served by the destination itself. The ownership value belongs to the workspace on the instance that issued it, so the destination lists its own value: publish that value in the `TXT` record, and point the root at the destination. A tracking host is part of each mailbox's settings and travels with the mailbox; it arrives unverified, and the destination's hourly check verifies it once the `CNAME` points at the destination. Forwarding set through a vendor lives at the vendor and is unaffected by a move. + + ## Related guides diff --git a/docs/content/docs/guides/sequences.mdx b/docs/content/docs/guides/sequences.mdx index caaf6cbbf..563201b7f 100644 --- a/docs/content/docs/guides/sequences.mdx +++ b/docs/content/docs/guides/sequences.mdx @@ -3,8 +3,64 @@ title: "Sequences & steps" description: "Multi-step sequences, follow-ups, A/B variants, and reply branching." --- +A sequence is the series of emails a campaign sends each contact: the first email, then follow-ups spaced over days. You build it on a visual canvas where each step is a card, and the lines between cards decide who goes where next. + +![The Steps canvas: the first email, then follow-ups that reply in the same thread, each with its wait on the line](/guides/campaign-steps.webp) + +## Build a sequence + + + + + +Open the campaign's **Steps** tab. The first email sits below the **Start** card. + + + + + +Click a card to write that step's subject and body in the side panel. + + + + + +Press the **+** at the end of a path to add a follow-up after it. + + + + + +Click the line into a step to set how many days it waits. + + + + + +Turn on **Stop on reply** in the flow toolbar, so a reply ends the cold follow-ups. + + + + + +## Good to know + +- Follow-ups reply in the same conversation by default. Turn off **Reply in thread** on a step to start a fresh one. +- A wait is 0 to 60 days and is counted per contact, so one contact waiting holds up no one else. +- A sequence holds up to 50 steps, and a step can be A/B tested with up to 5 variants on top of the Original. +- Auto-replies and out-of-office messages are not human replies and do not stop the sequence. +- **Send test** mails the saved step, not unsaved edits, so save first. + + + +## What a sequence is + A campaign sends a sequence of steps to each contact: the first email, then follow-ups spaced over days. You build it on a visual canvas where each step is a card and the lines between cards decide who goes where next. + + + + ## The flow canvas The canvas lays itself out from where a contact enters, top to bottom, and fills the window. Scroll with two fingers or drag empty space to move around; pinch, or hold `Ctrl`/`Cmd` and scroll, to zoom. `Shift+1` fits the whole flow. The **Start** card, `Contact enters the campaign`, sits at the top and carries how long their first email waits after they entered (the same setting as the campaign's **Settings** > **First email**; see [the campaign guide](/guides/campaigns/#delay-before-the-first-email)). Below it comes the first email, then whatever follows it. You never draw lines or arrange cards. @@ -20,6 +76,10 @@ The canvas lays itself out from where a contact enters, top to bottom, and fills The campaign wizard connects the follow-ups written there in order, with their waits set. + + + + ## Email steps A step has an internal **name** (never seen by recipients), a **subject** with a variable menu for merge fields like `{{.FirstName}}`, and a rich-text **body**. @@ -152,6 +212,10 @@ Under **What AI can see**, each step controls whether the model also receives th Switch steps always run on the normal schedule, so an instant reply branch leading into one pauses until the next boundary. + + + + ## Wait and spacing Spacing belongs to the **target** step, not a separate node, so there is no wait block on the canvas. The line into a step shows its wait (`3 days`); click it to set the days before that step (0 to 60). A step with no wait shows nothing on its line. The one delay that is not a step's own is the wait before the *first* email, on the line under the Start card. @@ -164,6 +228,10 @@ Waits are per contact. A contact waiting three days for a follow-up does not hol A path that runs instantly fires the moment the event happens, so the target's wait does not apply and the control is hidden. Action steps run right away. An email step is due immediately and goes out on the campaign's next scheduling pass, usually within 15 minutes, still inside your sending hours and the mailbox's spacing between sends. A path that is not instant, including one you toggled off, keeps the target's wait. + + + + ## Follow-ups and threading A follow-up is the next email step, sent as a **reply on the conversation the contact is already in** rather than as a second email out of nowhere. It carries `In-Reply-To` and `References` pointing at the last message they received from the campaign, and on Gmail it is posted into that same thread, so it sits under the first email in their inbox and in your Sent folder. @@ -180,6 +248,10 @@ Each email step has a **Reply in thread** switch, on by default. Turn it off for They point at the message id the sending provider put on the wire, which Warmbly learns when the worker reports the send. If that confirmation never arrives the follow-up still sends, carrying the conversation's subject, but as its own conversation. + + + + ## A/B variants Open a step's editor and click **A/B test this step**. @@ -192,6 +264,10 @@ Open a step's editor and click **A/B test this step**. Once variants have sent, each card shows Sent, Open rate, Reply rate, and Bounce rate. When Warmbly finds a **winner**, a banner names it with the rule and confidence used, and the winning card gets a trophy. + + + + ## Branching A branch routes contacts by condition. Press **+** under a step and pick **If / else**, or choose **Add a condition** from the step's **⋯** menu, then pick the condition in the side panel and press **Save**; nothing changes for contacts until you do. Each condition is a column under the step, checked left to right like `if` / `else if` / `else`, and the first match wins; **Check earlier** and **Check later** in the path panel change the order. Anyone matching nothing goes on under **Otherwise**, or stops if nothing is there. @@ -212,6 +288,10 @@ A branch routes contacts by condition. Press **+** under a step and pick **If / Reply intent, `opened`, and `clicked` can fire **instantly**, the moment the event lands rather than at the next scheduled step. A `reply intent is` path fires the moment the tagged reply lands, with no model call at routing time. These show a violet `instant` badge, and you can toggle it off per path to check at the next boundary instead. Instant still respects the window: it runs when they open or click within the N days, and never runs if they don't. + + + + ## Reply branches **If they reply** in the step picker opens a new path that fires the instant the contact replies (a positive reply; change it in the path panel). Press **Save** to add it, then add the steps it should run under it. @@ -221,6 +301,10 @@ Reply intent, `opened`, and `clicked` can fire **instantly**, the moment the eve - Auto-replies and out-of-office messages are not human replies: they trigger no human-reply path and do not stop the sequence. Use the `auto-reply / out of office` condition to react to them. - An out-of-office message does, on its own, hold the contact's next step until they are back, in every campaign they are a lead of, so no follow-up is sent to an empty desk. See [out of office and pausing one lead](/guides/campaigns/#out-of-office-and-pausing-one-lead). + + + + ## Stop on reply A campaign-level toggle in the flow toolbar. When on and a contact replies, the rest of the cold sequence stops for them while the reply branch on the email they answered still runs instantly. @@ -237,6 +321,8 @@ When an upgrade repairs a reply marker that was provably created by a synchroniz With it off and more than one step, Warmbly warns you on the canvas: replies that don't match a reply branch would otherwise keep receiving cold follow-ups. Turning it on still runs your reply branches, so it is strictly safer. + + ## Related diff --git a/docs/content/docs/guides/slack.mdx b/docs/content/docs/guides/slack.mdx index 66a759094..61b060e25 100644 --- a/docs/content/docs/guides/slack.mdx +++ b/docs/content/docs/guides/slack.mdx @@ -3,6 +3,50 @@ title: Slack description: "Ask the Warmbly assistant from Slack, work inbox replies in a Slack channel, and route notifications to the right channels." --- +Warmbly for Slack brings the AI assistant, your inbox replies and Warmbly notifications into the Slack workspace your team already uses. + +## Connect Slack + + + + + +Open **Integrations > Slack** and select **Connect**. + + + + + +Pick the Slack workspace and approve the app's permissions. + + + + + +Mention `@Warmbly` or message it to link your Slack account. When your Slack email matches your Warmbly account, it links you automatically. + + + + + +Under **Integrations > Slack**, pick a **Default channel** for notifications and, if you want, an **Inbox channel**. + + + + + +## Good to know + +- Connecting and changing Slack settings need the **Manage settings** permission. +- The assistant acts as you, with your permissions, and asks before changing anything. +- Anything that sends mail to a real recipient needs your approval every time. +- Every member of the inbox channel can read the mail posted there. +- On a self-hosted instance, the operator has to create a Slack app first. + + + +## What Warmbly for Slack does + Warmbly for Slack brings three things into the Slack workspace your team already lives in: - **The assistant.** Ask the same [AI assistant](/guides/ai-assistant/) you use in the dashboard by mentioning `@Warmbly` or messaging it, the way you would ask a teammate. It is the same assistant, with the same tools and the same answers, and it acts as you, with your permissions, and asks before changing anything. @@ -11,6 +55,10 @@ Warmbly for Slack brings three things into the Slack workspace your team already The app's **Home** tab shows whether your Slack account is linked, with buttons to link or unlink, ask Warmbly, and open Warmbly. + + + + ## Connect Slack Open **Integrations > Slack** and select **Connect**. Slack asks you to pick the Slack workspace and approve the app's permissions, then returns you to Warmbly. Connecting and changing Slack settings need the **Manage settings** permission. @@ -21,6 +69,10 @@ When the connection was made before Warmbly asked for a permission it now uses, On a self-hosted instance the Slack card stays unavailable until the operator creates the instance's own Slack app. See [Slack app](/development/slack-app/). + + + + ## Link your Slack account The assistant and the inbox buttons do things in Warmbly on your behalf, so Warmbly has to know which Warmbly member you are. Linking ties your Slack account to your Warmbly account in this workspace. Until you link, the bot can post notifications and inbox replies but will not act for you. @@ -44,6 +96,10 @@ Matching by email needs the `users:read` and `users:read.email` permissions. A c A Slack account links to one Warmbly account at a time. Linking again replaces the previous link. Unlink from the app's Home tab or from **Integrations > Slack** in Warmbly. If your membership in the workspace ends, the link stops working and the bot asks you to link again. + + + + ## The assistant ### Where to ask @@ -104,6 +160,10 @@ Every Slack conversation is also in your assistant history in the dashboard, and - **Slack Connect channels.** In a channel shared with another organization the assistant declines, because people outside your company would read what it says about your workspace. - **When the workspace limits it.** Under **Integrations > Slack**, a member with **Manage settings** can set the assistant to **On**, **DMs only** (it answers in DMs and the assistant pane, never in channels) or **Off**. + + + + ## Inbox in Slack Pick an **Inbox channel** under **Integrations > Slack** and incoming mail from the [Unibox](/guides/unibox/) is posted there, so the team can work replies without leaving Slack. Leave it empty and nothing is posted. @@ -141,6 +201,10 @@ Every button except **Open in Warmbly** needs a linked Slack account and the **U Typing in a conversation's thread is team discussion. It never sends email, and the assistant does not read it. Mention `@Warmbly` in the thread to ask the assistant about the conversation: it starts with that conversation as context, and still asks before sending anything. + + + + ## Notifications Warmbly posts a notification to Slack when the category is enabled for Slack in the recipient's **Settings > Notifications**. A notification meant for several teammates is posted once, not once per person. @@ -159,6 +223,10 @@ Each post carries an **Open in Warmbly** button. A **Reply received** notificati To work every reply in Slack rather than be pinged about it, use the [inbox channel](#inbox-in-slack) instead. + + + + ## Permissions | Action | Who | @@ -173,6 +241,10 @@ To work every reply in Slack rather than be pinged about it, use the [inbox chan Removing a member from the workspace ends what their Slack account can do here, because everything runs as their Warmbly membership at the time. + + + + ## What the bot reads The bot acts only on messages addressed to it: @@ -184,6 +256,10 @@ The bot acts only on messages addressed to it: Slack delivers other messages from channels the bot is in, including the team's discussion in inbox threads, and Warmbly ignores them. When you mention the bot inside an existing thread, or use the message shortcut, it also reads that thread's earlier messages so it knows what you are asking about. Those messages are passed to the assistant as quoted material, never as instructions, so text in a thread cannot tell the assistant to do something. The same holds for email text in the inbox channel. + + + + ## Troubleshooting | What you see | What to do | @@ -201,3 +277,5 @@ Slack delivers other messages from channels the bot is in, including the team's | A reply you expected is not in the inbox channel | With **Replies** selected, auto-replies, bounces and mail that answers nothing are left out. Switch to **All** to post every inbound message | | The bot answers without the thread's earlier messages | Slack limited how often the app may read message history, so it answered with only your message. Paste the relevant text into your question, or try again in a minute | | A notification did not reach a private channel | Invite the bot to that channel | + + diff --git a/docs/content/docs/guides/team-roles.mdx b/docs/content/docs/guides/team-roles.mdx index fee8e392d..c815ad680 100644 --- a/docs/content/docs/guides/team-roles.mdx +++ b/docs/content/docs/guides/team-roles.mdx @@ -3,12 +3,60 @@ title: "Team & roles" description: "Invite members and control access with roles, permissions, and resource scopes." --- +Share a workspace with your team: invite people by email and give each one a role that decides what they can see and change. + +## Invite a teammate + + + + + +Open **Settings > Members**. + + + + + +Type addresses into **Emails** in the **Invite teammates** box. + + + + + +Pick one or more **Roles**, and optionally narrow their **Access**. + + + + + +Press **Send invitations**. + + + + + +## Good to know + +- Every workspace starts with **Admin**, **Manager** and **Viewer** roles, which you can edit, delete or add to. +- **Owner** is a membership status, not a role, and there is exactly one per workspace. +- A member with several roles gets the union of all of them. +- A member limited to **Selected resources** has read-only access. +- Inviting, re-roling and removing members needs the **Manage team** permission. + + + +## Where team settings live and who manages them + Share a workspace with your team: invite by email, give each person a role, and the role decides what they can see and change. An optional access scope narrows which campaigns and mailboxes the role applies to, so a client or contractor can work inside your workspace without seeing the rest of it. The roster and invitations live under **Settings > Members**; roles and the permission matrix under **Settings > Roles & access**. Inviting, re-roling, removing, and managing roles all need the **Manage team** permission (the owner and the seeded Admin role have it; any custom role can carry it). Without it the roster is read-only. + + + + ## Inviting members In the **Invite teammates** box, paste or type addresses into **Emails**, separated by comma, semicolon, space, or Enter. Each becomes a chip: valid ones grey, malformed ones red so you can remove them first. Pick one or more **Roles** for the batch (a description on the right says what you are granting), optionally narrow the batch's **Access** (see [access scope](#access-scope)), then **Send invitations**. A restricted invitee is restricted from the moment they join; there is no window in which they see the whole workspace. @@ -33,6 +81,10 @@ Two things work differently when you run Warmbly yourself. Both are covered in [accounts and access](/development/accounts-and-access/#without-a-mail-relay), along with how to point the instance at a real relay. + + + + ## The roster Each row shows email, role, access, and join date, with your own row tagged "you". Open the role picker on a row to tick the roles that member holds; a member can hold several, and their effective access is the union. The owner's row is a fixed Owner badge, and your own row is not editable. @@ -41,6 +93,10 @@ To remove someone, hover (or tap) their row and click the remove icon. **You can Removal takes effect at once, including for someone who is signed in right now. Their open dashboard loses the workspace on its next request and its live updates stop, and they are asked to pick another workspace they belong to. Apps they authorized in the workspace are disconnected. Nothing they created is removed with them: mailboxes, contacts, campaigns and API keys belong to the workspace, so revoke their keys under **API keys** if those should go too. A changed role or a new owner reaches open dashboards' live updates without a reload. + + + + ## Access scope A role says what a member may do. Their access scope says which resources it applies to: @@ -73,6 +129,10 @@ Everything that cannot yet be narrowed to a scope is unavailable to them, and sh Changing a member's access needs **Manage team** and a recent sign-in, takes effect on their next request, and applies to their open dashboard and live updates without a reload. You must hold every permission the member's roles grant, the owner always reaches the entire workspace, and you cannot change your own access. Grants on a deleted folder, campaign or mailbox disappear with it. + + + + ## Roles Roles are workspace data. Every workspace starts with three seeded roles that are ordinary roles in every respect: rename, recolor, re-permission, or delete them, and add your own. **Owner is a membership status, not a role**, and there is exactly one per workspace. @@ -107,6 +167,10 @@ How the seeded roles map: **Viewer** holds only the two view permissions plus an A role also bounds what its members hand to software. An API key a member creates, and an app a member authorizes over OAuth, only gets the [API permissions their role covers](/api/permissions/#what-a-role-can-delegate), and an app's access shrinks with the member's role if it changes later. Members with **Manage API keys** or **Manage settings** can see every member's authorized apps under **Settings > OAuth apps > Authorized apps** and revoke any of them. + + + + ## Custom roles On **Roles & access**, click **New role**: a name (up to 50 characters, and only "owner" is reserved), a color, and an optional description. Pick a template under **Start from** to copy a permission bundle, then toggle individual permissions. Assign it from the roster or the invite flow. @@ -121,6 +185,8 @@ Five rules keep this safe: Custom roles apply everywhere permissions do: API access checks, dashboard visibility, and which realtime events a member receives. For a member with a restricted [access scope](#access-scope), only a role's read permissions take effect. + + ## Related guides diff --git a/docs/content/docs/guides/unibox.mdx b/docs/content/docs/guides/unibox.mdx index 7bb0f549b..484a07b39 100644 --- a/docs/content/docs/guides/unibox.mdx +++ b/docs/content/docs/guides/unibox.mdx @@ -3,12 +3,62 @@ title: "Unibox" description: "A unified inbox across all connected mailboxes, with labels and threading." --- +Unibox is one inbox for every connected mailbox. You read, sort and reply from a single screen instead of logging into each account. + +![The unibox: folders, mailboxes and labels on the left, the conversation list, and the open thread](/guides/unibox.webp) + +## Reply to a conversation + + + + + +Open **Inbox** from the main navigation. + + + + + +Pick a folder, view or mailbox in the **scope rail** on the left. + + + + + +Click a conversation in the list to read the whole thread. + + + + + +Reply in the thread. **From** starts on the mailbox that holds the message. + + + + + +## Good to know + +- Verified warmup mail stays out of Unibox and does not raise the unread count. +- Archive and Delete also file the conversation in Gmail, Outlook or your IMAP mailbox, and Delete only moves mail to the mailbox's own trash. +- Remote images in mail you receive stay hidden until you click **Load images**. +- Instant sends wait 30 seconds by default, so you can **Cancel** one before it goes. +- Nothing needs refreshing: new mail and unread counts update on their own. + + + +## What the inbox shows + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. One inbox for every connected mailbox. Read, sort, and reply from a single screen instead of logging into each account. Three columns: a **scope rail** for picking what to look at, a **conversation list**, and a **thread view** where you read and reply. Verified [warmup mail](/guides/warmup/) stays out of Unibox, including your mailbox's Sent copies and warmup run by [Warmbly Cloud](/guides/warmbly-cloud/). It does not create inbox notifications, raise the unread count or trigger reply classification. The consumer also checks existing messages in batches (once a day and after every restart) and removes confirmed warmup from Unibox, and any reply notification it had raised goes with it. This leaves the messages in your email provider and keeps ordinary conversations intact. + + + + ## Layout The top navigation shows your workspace and **Inbox**, without mailbox, folder or message identifiers. When a scope or conversation is open, click **Inbox** in the breadcrumb to return to the default inbox view. On phones, the header keeps the workspace picker and hides the section breadcrumbs. @@ -31,6 +81,10 @@ On a screen narrower than 1024px the contact panel is an overlay rather than a c The contact panel shows the person on the other side of the conversation: the first sender who is not one of the workspace's mailboxes. It finds their contact by address first. When that address is not a contact but the conversation answers one of your campaign emails (people often reply from an alias, a second domain or a colleague's account), the panel shows the lead that campaign wrote to, with a **Replied from** line naming the address the reply actually came from. Only when neither matches does it offer **Add as contact**. + + + + ## Keyboard The conversation list takes the keyboard, so a triage pass never needs the mouse. Press `?` anywhere in Warmbly for the full list; these are the ones that act on the inbox. @@ -48,6 +102,10 @@ The conversation list takes the keyboard, so a triage pass never needs the mouse The shortcuts modal only lists what the screen you are on can actually do, so these rows appear while the inbox is open and not elsewhere. + + + + ## Folders The rail is two short groups and then your mailboxes. The first group is where you work: **All mail**, **Inbox**, **Unread**, **Awaiting reply**, **Agent drafts**, **Snoozed**. The second holds the rest of the mail folders, plus **Scheduled** alongside them, so direction is visually clear instead of one combined list: @@ -73,6 +131,10 @@ Spam and Trash stay out of every other view, and so does Archive: filing a conve The initial import that runs when a mailbox is first connected covers Inbox, Sent, Archive, and Drafts. It deliberately skips the existing contents of Spam and Trash, whose history would consume the import budget that belongs to real conversations. Both folders fill normally from the moment the mailbox is connected, so they show what arrives from then on rather than what was already there. + + + + ## Scopes | View | Shows | @@ -109,6 +171,10 @@ It searches the scope you are in. When that finds nothing, the empty state offer The filter button next to the title opens a small panel that narrows the list on the spot: read state, a date range (today, the last 7 or 30 days, or two dates), sender, labels, mailboxes (or a whole tag of them), and sort order. Anything you add shows as a chip under the search box, and each chip removes itself with one click. A view that already fixes one of these, such as Unread or a single mailbox, does not offer that control again. + + + + ## Threading Each row is a conversation, not a message, with a count next to the sender when more than one message is inside. Opening it shows every message in order, with the message count and the owning mailbox under the subject. Replies and new inbound mail land in the same thread. Rows group under `Today`, `Yesterday`, `This week`, and `Earlier` headers. @@ -123,6 +189,10 @@ Threading applies on both sides. A reply carries an `In-Reply-To` header naming `j` / `k` move between rows, `Enter` opens, `Esc` closes, `/` focuses search, `c` labels. Press `?` for the full list. + + + + ## Reading a message The newest message in a conversation opens expanded, along with anything still unread. Older messages collapse to their preview line; click the header (or the preview) to open one. Bodies load per message, so a long thread does not pull down every message at once. @@ -141,6 +211,10 @@ Recognized quoted history collapses behind **Show quoted text**. Click it to rea Every open message has a details toggle on its recipient line, the `to` under the sender's name, and the sender's name and address open the same panel. It shows the message envelope in place: every From, Reply-To, To, Cc and Bcc address, when it was sent and, if that differs, when the mailbox received it, both in your own time zone, the mailbox and folder it lives in, its size, and the Message-ID and In-Reply-To headers. Click any address or identifier to copy it. The info icon next to Reply and Forward opens the same panel, including on a message that is still collapsed. + + + + ## Labels Labels are one workspace list, used on [contacts](/guides/contacts-crm/#labels), on conversations here and by [forms](/guides/forms/), so `Interested` means the same thing on a contact as it does on a conversation. The list is shared with the rest of the team too: a label anyone creates is available to everyone, and a conversation one member labels shows that label to the next. Label with the tag button in the conversation header or `c`: search existing labels, tick what applies, or type a name and **Create**. A conversation can carry several. @@ -151,6 +225,10 @@ Labeled conversations show colored chips on the row and in the header, and each Labels mark conversations and contacts. Mailbox tags mark the mailboxes themselves (grouping accounts by client or domain). Both filter from the rail, but they describe different things. + + + + ## Read state A conversation is unread when any message inside is unseen, marked by a blue dot in the margin, bolder text, and a blue timestamp. Opening it marks its messages seen and updates the counts, and so does **Mark as read** on the row or on a selection, without opening anything. @@ -161,6 +239,10 @@ Read state travels both ways. A message arriving already read is stored that way The relay runs on the worker that holds the mailbox, just after Warmbly's own copy is updated, so the dashboard never waits on it. If the mailbox is between workers at that moment, or the provider refuses, Warmbly keeps your read state and the mailbox keeps its own; nothing retries, because the next sync reports whatever the provider actually thinks. + + + + ## Filing a conversation The thread header carries three filing actions, on the row above the message on a wide screen and behind the three-dot menu on a narrow one: @@ -205,6 +287,10 @@ The move runs on the worker that holds the mailbox, just after Warmbly's own cop To keep filing inside Warmbly for one mailbox, turn off **Mirror Archive and Delete** on its Settings tab (see [mailboxes](/guides/mailboxes/#filing-in-the-mailbox)). Filing then only moves the conversation here, and the next sync will not undo it: Warmbly records where the provider has each message separately from where you filed it, and follows the provider only when the provider itself moves the message. So archiving or junking a message in Gmail still reaches Warmbly either way, and an ordinary sync pass does not. + + + + ## Replying Reply, forward, or hover any single message to reply to it specifically. The composer appears when you ask for it or return to a conversation with a saved reply or forward. You can apply a saved **template** or **Insert booking link**. A template with an HTML body keeps its formatting; see [templates and HTML](#templates-and-html). @@ -249,6 +335,10 @@ Set your voice profile under **Settings, Workspace**: what you sell, who to, and For drafts written automatically on every inbound reply and held for approval, turn on the [inbox agent](/guides/inbox-agent/). It never sends on its own. + + + + ## Composing **Compose** at the top of the conversation list, the `n` key anywhere, or the **Email** action on a contact opens a docked window so you can keep browsing while writing. @@ -291,6 +381,10 @@ With a recipient set, **History** slides out every conversation you have had wit Scheduled and smart-queued sends skip the undo window, since they already wait. Cancel them from the **Scheduled** view, or via `DELETE /unibox/scheduled/:task_id`. + + + + ## Realtime Nothing needs refreshing. New mail appears in the list, updates the open thread, and raises the unread count on its own. The open conversation updates as soon as its mail changes; rail counts, lists and analytics catch up within a couple of seconds, so a burst of incoming mail refreshes them once instead of once per message. The count on **Unibox** in the navigation (and in the browser tab) is the number of unread conversations in **Inbox**, the view it opens, so it never points at mail that view does not list: sent mail, drafts, archived, snoozed and automated conversations are left out. Read-state changes, replies, scheduled sends, snoozes, and label changes all refresh the affected views, keeping rail counts, the list, and the open thread consistent. @@ -299,6 +393,8 @@ Nothing needs refreshing. New mail appears in the list, updates the open thread, Replying promptly supports sender reputation, which warmup and good deliverability practice protect. See [Deliverability](/guides/deliverability/). + + ## Related diff --git a/docs/content/docs/guides/unsubscribe.mdx b/docs/content/docs/guides/unsubscribe.mdx index c6b91fb73..23f980d64 100644 --- a/docs/content/docs/guides/unsubscribe.mdx +++ b/docs/content/docs/guides/unsubscribe.mdx @@ -3,8 +3,56 @@ title: Unsubscribe and suppression description: How recipients opt out of campaign email, what Warmbly does when they do, and how the suppression list works. --- +Every campaign email gives its recipient a way to stop hearing from you. Every opt-out is honoured automatically across the whole workspace. + +## Choose how recipients opt out + + + + + +Open **Settings > Sending > Unsubscribe** to set the workspace's opt-out mode. + + + + + +Pick **Reply to opt out** (the default), **Unsubscribe link**, or **Nothing**. + + + + + +Change the wording of the sentence or the link text if you want. + + + + + +To use a different mode for one campaign, set it under **Preferences** on that campaign. + + + + + +## Good to know + +- A reply asking to stop, such as "remove me", puts the sender on the suppression list. A reply saying "not interested" does not. +- **Contacts > Suppression list** is workspace-wide: no campaign emails an address on it. +- A bare domain such as `acme.com` suppresses every address at it. +- An unsubscribe link opens a confirmation page, and nothing happens until the recipient presses its button. +- On a self-hosted instance, `API_PUBLIC_URL` must be an address recipients can reach. + + + +## What this page covers + Every campaign email gives its recipient a way to stop hearing from you, and every opt-out is honoured automatically across the whole workspace. This page covers the three mechanisms, the suppression list behind them, and the settings that shape what a recipient sees. + + + + ## What a recipient sees Warmbly appends an opt-out to every campaign email, after the signature. There are three modes, set for the workspace under **Settings > Sending > Unsubscribe** and overridable per campaign under **Preferences**: @@ -55,12 +103,20 @@ A campaign with **Plain text only** switched on ships no HTML at all, so there i The answer there is the header. It is not visible copy, it works the same on a plain-text send, and it is what Gmail and Yahoo actually look for. Leave **Unsubscribe header** on, leave the opt-out line as the reply sentence, and the recipient gets a one-click control in their mail client plus a human sentence in the message. + + + + ## The List-Unsubscribe header Independently of the body, each campaign can attach the `List-Unsubscribe` and `List-Unsubscribe-Post` headers (RFC 8058). Mail clients that recognise them show their own **Unsubscribe** control next to the sender's name, and pressing it opts the recipient out with no page at all. The toggle is **Preferences > Unsubscribe header**, on by default. Two things to know. Gmail only shows its button for mail it already classes as bulk, so a one-to-one style cold email from a Workspace mailbox usually does not get one. And Google and Yahoo require the header only above roughly five thousand messages a day to their consumer inboxes, which no mailbox at Warmbly's default limits approaches. The header costs nothing and is worth leaving on; it is not a substitute for the opt-out line. + + + + ## Replies that ask to stop Warmbly reads every reply to a campaign email. One that asks to stop, in wording such as "unsubscribe", "remove me", "stop emailing me", "opt out" or "do not contact", puts the sender on the suppression list immediately. Replies in every tagging language are read the same way, whatever languages the workspace tags in: "bitte austragen", "désinscrivez-moi", "denme de baja", "wypiszcie mnie", "отпишите меня" or "配信停止" opt the sender out too. The quoted history is cut in each of those languages as well, so a campaign that carries its own opt-out line in German or Japanese does not opt out everyone who answers it. Only the new text of the reply is read: the quoted history below it carries your own opt-out wording and is ignored. Phrases match on whole words, so "stop by our booth" does not opt anyone out. @@ -77,6 +133,10 @@ A reply that says "not interested" is classed as negative and can stop the seque Turn this off with **Settings > Sending > Honour replies that ask to stop** if you would rather handle such replies by hand. Leaving it off with the reply-to-opt-out line in place means promising an opt-out you then honour manually. + + + + ## The suppression list **Contacts > Suppression list** is every address and domain that no campaign in the workspace will email, however it got there: @@ -99,12 +159,20 @@ The list is workspace-wide. An entry stops the address in every campaign, in new Suppression entries travel with a [workspace export](/guides/workspace-export-import/), so a workspace moved to another instance does not re-mail people who already opted out. + + + + ## Webhooks and the API An opt-out fires the `campaign.unsubscribed` [webhook](/guides/webhooks/) with a `source` of `one_click` (the mail client's button), `link` (the link in the email), `reply` (a reply asking to stop) or `action` (a sequence's **Unsubscribe** step). The suppression list is available over the API as `GET /suppressions`, `POST /suppressions` and `DELETE /suppressions/:id`. See [deliverability and ops](/api/reference/deliverability-ops/#list-the-suppression-list). + + + + ## Self-hosting Unsubscribe links are served by the API process on the origin in `API_PUBLIC_URL`, so that variable must be set to an address recipients can reach. Leaving it unset does not disable the link: the backend falls back to its own listen address, `API_HOST`, whose default wildcard bind resolves to `http://localhost:8080`. Every opt-out in every campaign email then points at an address only the backend's own machine can open. @@ -114,3 +182,5 @@ A link minted today is a row in the database, so it survives a secret rotation a Nothing in a campaign email points at warmbly.com. The opt-out address, the tracking pixel and every wrapped link are built from your own `API_PUBLIC_URL` and `TRACKING_DOMAIN`, and a workspace on your instance that verifies its own tracking domain gets the opt-out on that domain instead. A workspace's verified domain is a CNAME to your `TRACKING_DOMAIN`, so the tracking service serves the unsubscribe routes as well as the pixel and click tickets: an install running [core only](/development/install/) has no tracking service, keeps `TRACKING_DOMAIN` unset, and serves every opt-out from the API address. One-click (`List-Unsubscribe-Post`) is attached only when the link is `https`. An instance reachable over plain HTTP still sends the plain `List-Unsubscribe` header, because a provider POSTing an opt-out to an `http` address either refuses it or sends the token in the clear. + + diff --git a/docs/content/docs/guides/warmbly-cloud.mdx b/docs/content/docs/guides/warmbly-cloud.mdx index f6ecd11a6..167f9aaa7 100644 --- a/docs/content/docs/guides/warmbly-cloud.mdx +++ b/docs/content/docs/guides/warmbly-cloud.mdx @@ -3,6 +3,44 @@ title: Warmbly Cloud for self-hosted instances description: "Warm the mailboxes on your own Warmbly instance in the hosted warmup pool, while campaigns, contacts and inbox stay on your server." --- +Linking a self-hosted instance to Warmbly Cloud lets its mailboxes warm in the hosted pool, instead of only against the few other mailboxes on your server. Only warmup moves to the cloud: campaigns, contacts, the unibox, tracking and every stored message stay on your server. + +## Link a workspace + + + + + +On your instance, open **Settings > Warmbly Cloud**, select the local workspace, and press **Connect to Warmbly Cloud**. + + + + + +Press **Open Warmbly Cloud**, sign in with your Warmbly account, enter the eight character code and pick the workspace to link to. + + + + + +Back on your instance, switch on the mailboxes you want warmed. + + + + + +## Good to know + +- A free Warmbly Cloud workspace warms up to 10 mailboxes at no cost. The Warmup plan ($15 a month or $144 a year) moves them to the premium pool with no mailbox cap. +- Connecting needs an instance administrator signed in with two-factor authentication. +- A code expires after 15 minutes if nobody approves it. +- The cloud reaches your mail server over the internet, so SMTP and IMAP both need TLS or STARTTLS. +- A mailbox the cloud quarantines or blocks is skipped by your campaigns until the hold ends. + + + +## What linking gives you + A self-hosted Warmbly instance warms mailboxes only against the other mailboxes on that same instance, which for most installs is a handful. Linking the instance to Warmbly Cloud lets those mailboxes warm in the hosted pool instead: thousands of real, monitored mailboxes exchanging natural-looking mail, with replies and inbox engagement handled for you. Only warmup moves to the cloud. Campaigns, contacts, the unibox, tracking and every stored message stay on your server exactly as before. Linked instances also get Google and Microsoft sign-in through Warmbly's own OAuth apps, so there is no client to register: see [Google and Microsoft mailboxes](#google-and-microsoft-mailboxes-sign-in-through-warmbly-cloud). They can also have Cloud serve a sending domain's [root redirect](#root-redirects-served-by-warmbly-cloud), so the domain answers visitors without any proxy setup on your server. @@ -11,6 +49,10 @@ Only warmup moves to the cloud. Campaigns, contacts, the unibox, tracking and ev A free Warmbly Cloud workspace holds up to 10 mailboxes, connected directly or through a linked instance, and warms them at no cost. The Warmup plan, $15 a month per workspace or $144 a year, moves the workspace's mailboxes to the premium pool for better deliverability, with reputation protection and no mailbox cap. Nothing else about self-hosting changes. + + + + ## How it works New connections link one local workspace to one Warmbly Cloud workspace. A Cloud workspace can have only one active self-hosted connection, including any older instance-wide link. New connections for different local workspaces need different Cloud workspaces, each with its own plan. Existing server-wide connections keep serving their local workspaces without reconnecting or buying another plan. Self-hosted workspace creation itself is unlimited; hosted creation limits and request-rate protection are unchanged. When you enroll a mailbox, the instance sends that mailbox's SMTP/IMAP credential to the cloud, sealed in transit and at rest with the same envelope encryption the hosted product uses for every customer mailbox. From then on: @@ -27,6 +69,10 @@ Arrivals awaiting verification are stored in your instance's database and retrie Unenrolling an SMTP/IMAP mailbox, disconnecting the instance, or deleting the mailbox altogether deletes its credential on the cloud immediately. Deleting an enrolled mailbox revokes its enrollment before the record goes, so the pool cannot be left holding a password for a mailbox your instance no longer has; if the cloud cannot be reached at that moment the delete is refused, the mailbox is put back as it was, and retrying it once the cloud is reachable completes it. A mailbox that was signed in through Warmbly Cloud stays in the cloud workspace when you remove it from the instance; disconnecting the instance removes its mirrors of such mailboxes, since they cannot send without the link. + + + + ## Warmup analytics on your instance Enrolled mailboxes show Cloud's daily sends, replies, received warmup mail and target volumes in **Mailboxes > Analytics**. Their warmup placement reports include daily inbox, tabs, spam and rescued counts, provider and host breakdowns, and the trailing inbox rate. Workspace reports combine Cloud-enrolled mailboxes with mailboxes warming locally. Earlier local warmup history stays visible too. @@ -37,6 +83,10 @@ Existing enrollments work without reconnecting or re-enrolling. Deploy the repor Once a mailbox is unenrolled, the instance no longer requests its Cloud history. SMTP/IMAP unenrollment deletes the Cloud mailbox and its warmup history; a Cloud-managed mailbox's history stays in its Cloud workspace. The instance's earlier local history is unchanged. + + + + ## Linking a workspace The link is offered on a self-hosted instance as the last step of onboarding when you first sign in, as a banner on **Mailboxes** until you connect or dismiss it, under **Settings > Warmbly Cloud** at any time, and on **Sending domains** when a [root redirect](#root-redirects-served-by-warmbly-cloud) could be served from Cloud. Whichever you use, the steps are the same: @@ -61,6 +111,10 @@ Disconnect is still deliberate and potentially disruptive. Disconnecting a share Deploy the Cloud backend and migration **before** publishing the self-hosted upgrade. Old self-hosted clients can keep using existing connections, including adding mailboxes, but cannot establish new instance-wide connections. New self-hosted clients refuse to connect to older Cloud versions that do not advertise workspace-scoped support. No new environment variable or data backfill is required for restoring use of existing connections. + + + + ## Google and Microsoft mailboxes: sign in through Warmbly Cloud On a linked instance, signing in one mailbox under **Add account > Google** or **Add account > Microsoft** no longer needs an OAuth client of your own. The sign-in window opens on a Warmbly Cloud page first. It names the address of the instance asking and the Cloud workspace the mailbox will join; press **Continue** and Warmbly's verified Google or Microsoft app takes over. If you did not start the sign-in yourself, close that page: the mailbox would join the workspace it names. When you approve: @@ -85,6 +139,10 @@ The cloud connects to your mail server over the internet, so both SMTP and IMAP A Google or Microsoft mailbox that was signed in with your instance's own OAuth app cannot be enrolled, because that grant can only be refreshed by the app that issued it. Remove it and add it again through Warmbly Cloud to warm it there. + + + + ## What your instance shows Once linked, the **Mailboxes** page marks every enrolled mailbox with a blue **Cloud** badge on its address and its avatar and a "Warmed by Warmbly Cloud" line under it (amber while paused), shows the cloud's daily count in the warmup column with the same warming animation local warmup uses, and offers **Warm in Warmbly Cloud**, pause, resume and remove in each row's warmup menu. The mailbox drawer's Warmup tab shows the same block with the cloud's numbers in place of the local controls. This is shown to members with the manage mailboxes permission. @@ -105,6 +163,10 @@ Warmbly Cloud sends the warmup mail itself, connecting to your mail server's SMT The count can also hold below the target when the pool has fewer partners free for the mailbox today than the ramp asks for, since a mailbox never writes to the same partner twice in a day. The Warmup tab says so with how many partners are available, and the cloud checks again every few hours. + + + + ## Free and Premium | Tier | Mailboxes | Partners | @@ -118,6 +180,10 @@ Your instance shows which tier it is on in two places: the plan badge in the hea The Warmup plan is the same plan a hosted workspace buys when it only warms and does not send, so it is not tied to self-hosting: a workspace on it can hold mailboxes connected on the cloud, through linked instances, or both, and the workspace's regular hosted plan, if it has one, also counts as Premium, so a workspace on Starter or above needs nothing extra. It is not listed next to Starter, Grow and Business because it is not an alternative to them; the button on your instance and the mailboxes page on the cloud are the ways in. See [billing](/guides/billing/#the-warmup-plan). + + + + ## Safety and enforcement Enrolled mailboxes are ordinary members of the hosted pool and are held to the same rules: verification tokens on every warmup message, complaint and bounce tracking with automatic quarantine or blocking when a mailbox starts hurting partners, and spam placement that slows a mailbox down. A mailbox that gets quarantined on the cloud stops being offered to partners and re-enters on the same probation terms as any hosted mailbox. @@ -144,6 +210,10 @@ These checks apply before new commands are admitted. Updating the control plane For mailboxes signed in through Warmbly Cloud the enforcement reaches sending too. Every access token the instance draws is checked on the cloud: a mailbox the cloud has **blocked**, one that is no longer active there, one that was removed from the workspace, or an instance whose link was revoked gets no token, and the instance stops sending and syncing from that mailbox as soon as its cached token expires. + + + + ## Placement tests on Warmbly Cloud's seed panel A self-hosted instance's own seed panel holds whatever its administrator added, often nothing. A linked instance can borrow Warmbly Cloud's instead: pick **Warmbly Cloud** as the panel when you start an [inbox placement test](/guides/placement-tests/), or for a campaign's placement monitor. @@ -157,6 +227,10 @@ The split is the same as for warmup, only narrower: The panel is offered only while the instance is linked. Without a link a test on it is refused with `placement_panel_unavailable`. + + + + ## Root redirects served by Warmbly Cloud A [root redirect](/guides/sending-domains/#root-redirects) sends visitors to a sending domain on to your website. Served from your own instance, the domain has to reach Warmbly through whatever proxy sits in front of it, which on a server behind Traefik, nginx or a hosting panel means routing each domain by hand. A linked instance can pick **Warmbly Cloud** under the redirect's **Served from** instead: Cloud answers for the domain from its own tracking service and issues its certificate, and all you add is DNS. @@ -168,6 +242,10 @@ A [root redirect](/guides/sending-domains/#root-redirects) sends visitors to a s See [serve it from Warmbly Cloud](/guides/sending-domains/#serve-it-from-warmbly-cloud) for how it looks on the sending domains page. + + + + ## Unlinking On your instance, **Settings > Warmbly Cloud > Disconnect** removes only the selected workspace link's enrollments and lets eligible local warmup take over after confirmed cleanup. Other workspace links and retained legacy links are unaffected. Instance-originated SMTP mailboxes and their credentials are deleted on the cloud. An OAuth mailbox belongs to the cloud workspace: unlinking removes that connection's access, not the workspace's mailbox or provider grant. Its native cloud warmup configuration also remains: pause or delete it separately on Warmbly Cloud if you want that participation or its stored credentials removed. Root redirects using the disconnected link stop at the same time. Removing a legacy instance-wide link remains a separate administrator action. On Warmbly Cloud, **Settings > Linked instances** lists the connections linked to a workspace and can unlink any of them from the other side. @@ -176,6 +254,10 @@ SMTP enrollment, mailbox removal and instance disconnect first record a durable Removal is an explicit opt-out. A late enrollment acknowledgment cannot reverse a pending removal. After removal finishes, a new explicit enrollment can create a fresh intent, but it does not reset reputation holds. Disconnect remains pending until remote revocation and local cleanup succeed, and no new enrollment is accepted during it. + + + + ## Managed sign-in recovery and consent New Gmail/Outlook sign-ins and adoption of an existing cloud mailbox use managed consent protocol 1. Both sides record a stable operation before acting. The instance records the workspace and initiating user, rechecks their current mailbox-management permission, and binds completion to that workspace, user, linked cloud instance, provider and mailbox identity. Organization owners are allowed; another member needs an accepted membership with `manage_emails`. Restricted or suspended workspaces cannot complete a managed operation. @@ -214,6 +296,10 @@ Redis still holds the short-lived browser session, PKCE verifier, browser bindin Consent restrictions/history travel in a [workspace export](/guides/workspace-export-import/). Import resets source instance IDs, remote IDs, session hashes, planned account handles and queued activation handles; live cloud account references on the local consent record are reset too. This prevents an imported pending operation from resuming against the source instance, while retaining its restrictive state. Workspace links and legacy instance-wide links do not travel: reconnect and explicitly authorize any new managed operation after moving. Imported history is not a fresh consent or a positive provider verdict. + + + + ## Self-hosting notes - Traffic is outbound only: the instance calls `WARMBLY_CLOUD_URL` (default `https://api.warmbly.com`) over HTTPS, on port 443 or 8443, and only to a public address. Plain HTTP to `localhost` is accepted only when `APP_ENV` is `dev`. Nothing needs to reach the instance from the internet @@ -222,6 +308,8 @@ Consent restrictions/history travel in a [workspace export](/guides/workspace-ex - Connections and enrollments are bound to this installation's encryption key, even when workspace-scoped, so they are not part of a [workspace export](/guides/workspace-export-import/). Reconnect after moving a workspace to a new instance - `INSTANCE_NAME` sets the name shown on the approval page; the hostname is used when unset. The name follows the workspace [naming rules](/api/error-codes/#name-refusals), so one that reads as a web address (a hostname, for example) shows as "Self-hosted instance" + + ## Related guides - [Warmup](/guides/warmup/) diff --git a/docs/content/docs/guides/warmup.mdx b/docs/content/docs/guides/warmup.mdx index 06c71cb00..1edb920bf 100644 --- a/docs/content/docs/guides/warmup.mdx +++ b/docs/content/docs/guides/warmup.mdx @@ -3,16 +3,70 @@ title: Warmup description: "Disclosed diagnostics, placement evidence, participation and conservative pacing." --- +Warmup sends short, disclosed test messages between participating mailboxes and records where they land. It reveals configuration and delivery problems, but it does not guarantee inbox placement. + +![A mailbox's Warmup tab: participation, today's sends against the ramp target, pool standing and the ramp settings](/guides/mailbox-warmup.webp) + +## Start warmup on a mailbox + + + + + +Open a mailbox from the Accounts list and go to the **Warmup** tab of its drawer. + + + + + +Start warmup to turn on diagnostic sending. + + + + + +Choose where warmup mail is filed: **Its own folder** (the default), **Leave it in the inbox**, or **Archive it**. + + + + + +Pause to stop sending but keep receiving, or Stop to turn off both while keeping the ramp history. + + + + + +## Good to know + +- Warmup is a paid feature: free organizations can connect mailboxes, but no warmup traffic is scheduled for them. +- New mailboxes start with warmup off. Once on, it starts at `10` emails a day by default and climbs by `+1` a day to a ceiling of `40`. +- Keep warmup on after campaigns start. A mailbox backing a live campaign drops to at most five warmup messages a day. +- Warmup mail never appears in the unibox, and it is removed from the mailbox after 30 days by default. +- Spam placement is judged at Google, Microsoft and Yahoo only. It slows a mailbox down but does not, on its own, quarantine or block it. + + + +## What warmup is and who gets it + Warmup sends disclosed automated diagnostics between participating mailboxes. It measures observed placement and bounded actual-message DKIM evidence. It does not establish organic engagement or guarantee reputation improvement or inbox placement. Free organizations can connect mailboxes, but no warmup traffic is scheduled for them; pending warmup activity is skipped rather than sent. + + + + ## Why it works Diagnostics reveal configuration and delivery problems, not whether real recipients welcome outreach. Provider acceptance is not inbox placement; synthetic replies are not human feedback. Missing observations remain unknown. + + + + ## How it works With warmup enabled, Warmbly repeatedly picks a partner mailbox (avoiding recent ones), decides whether to start a thread or reply to one, sends a short plaintext message through that mailbox's worker, then records stats and schedules the next send. @@ -57,6 +111,10 @@ Canonical turns use actual alternating configured identities and pinned subject/ The controller runs every six hours against the last seven days of demand: at least 200 active threads, up to 5,000, refreshing the most-used content, at most 250 threads per batch and 1,000 per day. Threads are retired automatically when at least 3 of 20 or more sampled deliveries land in spam and the rate is `15%` or higher. Admins monitor it but never refill it by hand. + + + + ## Pools A pool is the set of mailboxes that warm each other. Your mailbox exchanges warmup mail within its own pool, with the mirrored crossings documented below. Every instance has both pools from install, so a self-hosted instance warms within its own pools from the first mailbox; [Warmbly Cloud](/guides/warmbly-cloud/) is how it joins the hosted one instead. @@ -104,6 +162,10 @@ The **Analytics** tab of the same drawer charts the exchange over time: sent, re Pause preserves separate receiving permission; Stop disables both directions. Lost authority, disconnection, quarantine or block also restrict participation. Health alone is not consent. + + + + ## Seeing where warmup lands Every warmup email a mailbox sends is looked for in the partner's mailbox, and when it arrives Warmbly records where: the primary inbox, a Gmail category tab (Promotions, Updates, Social or Forums), or spam. Nothing is estimated or sampled, and the history is kept for the life of the mailbox rather than only the latest day. @@ -126,6 +188,10 @@ The rate is taken at Google, Microsoft and Yahoo only, the providers that judge Filter by provider to distinguish observations from gaps. Historical rescued counts are legacy requests, not confirmed moves or benefits. Diagnostic mode does not rescue spam. Unconfirmed means not observed after timeout, not verified failure. First-folder evidence is separate from housekeeping; absent historical folders stay unknown. Days are UTC. See [warmup placement](/api/reference/analytics/#get-warmup-placement). + + + + ## The ramp | Setting | Default | Meaning | @@ -162,6 +228,10 @@ Placement slowdown and ramp pause have different windows. The drawer shows obser Keep warmup on after campaigns start. A mailbox backing a live campaign drops to at most five warmup messages per day, leaving the campaign as primary traffic. Warmup does not make repetitive or unwanted copy safe: you still need real targeting, personalization, suppression, and unsubscribe support. + + + + ## Replies Configured diagnostic turns continue disclosed scenarios, not organic relationships. Exact pinned plans and subjects are reloaded, each parent is answered once, and exhausted, retired or missing threads close. @@ -172,6 +242,10 @@ Receiving warmup mail can also prompt an answer directly. When a verified warmup Scheduling respects windows, provider feedback, shared limits and spacing. Jitter distributes load, not human identities. Diagnostic mode schedules no synthetic read, star, importance or spam-rescue actions. + + + + ## Keeping warmup out of your inbox Warmup is real mail in a real mailbox, so there are two separate places it could show up: the Warmbly [unibox](/guides/unibox/), and your own mail client. Both are handled, and they are handled differently. @@ -233,6 +307,10 @@ Warmup writes to none of the tables the customer-facing views read, so nothing n - direct-mail analytics counts a mailbox's own sent and received mail and excludes every task-backed message, campaign and warmup alike - notifications are raised for domain authentication, worker downtime and campaign guardrails, never per message + + + + ## Pool safety Pools enforce permission and restrictive complaint/bounce/authentication evidence, current authority and durable holds. Deleting diagnostics or choosing spam is recipient withdrawal, not automatic tampering misconduct. @@ -277,10 +355,18 @@ On a self-hosted instance linked to [Warmbly Cloud](/guides/warmbly-cloud/#safet The standing follows the address, not the connection. Removing a mailbox from the workspace and adding it back, or dropping out of the pool and rejoining, does not clear its score or its block: the same address in the same workspace rejoins with the standing it left with. That standing is kept for 90 days after the block ends or after the mailbox was removed, whichever is later; a block that requires review never lapses. A mailbox in good standing leaves nothing behind. + + + + ## Graduating to cold sending Diagnostic-history mailboxes start cold sending at a conservative ceiling of five/day. Synthetic age never increases it. Recent confirmed human replies to actual campaign recipients permit bounded increases, subject to calendar days and negative feedback. Missing evidence leaves readiness/full-cap dates unknown. This product policy is not a provider-approved safe volume. See [campaign pacing](/guides/campaigns/). + + + + ## Recommended posture - Start new mailboxes around `10`/day and let the ramp climb slowly. @@ -289,6 +375,8 @@ Diagnostic-history mailboxes start cold sending at a conservative ceiling of fiv - Prefer more healthy mailboxes over pushing a few too hard. - Watch complaints and spam placement, not open-rate vanity metrics. + + ## Related guides diff --git a/docs/content/docs/guides/webhooks.mdx b/docs/content/docs/guides/webhooks.mdx index 592e337d3..a94099458 100644 --- a/docs/content/docs/guides/webhooks.mdx +++ b/docs/content/docs/guides/webhooks.mdx @@ -3,12 +3,66 @@ title: Webhooks description: Receive real-time HTTP callbacks when things happen in your Warmbly workspace. --- +Webhooks send a signed HTTP POST to your own URL when something happens in your workspace, such as a reply landing or a campaign completing. Use them to update your CRM or trigger other tools without polling the API. + +## Add a webhook + + + + + +Open **Settings -> Webhooks** and click **Add endpoint**. + + + + + +Enter the public HTTPS URL where Warmbly should POST events. + + + + + +Choose the events to subscribe to, or leave the list empty to receive all standard events. + + + + + +Click **Create** and copy the signing secret right away. It is shown only once. + + + + + +Answer Warmbly's test request with any `2xx` to verify the endpoint, and real events start. + + + + + +## Good to know + +- An unverified endpoint receives only a challenge request, not real events. +- High-volume **firehose** events (campaign sends, opens, clicks, inbox syncs) must be added explicitly. +- Check the `X-Warmbly-Signature` header on every delivery against the raw request body. +- A failed delivery is retried for up to 8 attempts, then marked abandoned. +- Every attempt is logged on the endpoint's **Deliveries** tab, where a failed one can be redelivered. + + + +## What webhooks are for + Browsing filters, searches and view choices are [remembered in your browser tab](/guides/browsing/) across refreshes and navigation, separately for each user and workspace. Webhooks let external systems react to Warmbly activity without polling the API. When an event fires (a reply lands, a campaign completes, a meeting is booked), Warmbly sends a signed HTTP POST to every matching endpoint your workspace has registered. From there you can update your CRM, trigger a Slack message, or hand off to any other tool. If you prefer an in-product alternative that works behind a firewall (no public URL required), the [automations](/guides/automations/) Fire Event step publishes custom events to the realtime socket instead. + + + + ## Setting up a webhook Open **Settings -> Webhooks** in the dashboard and click **Add endpoint**. @@ -22,6 +76,10 @@ The secret is encrypted at rest with the instance's credential key, the same one After creation the endpoint is **unverified** and will receive only a challenge request, not real events. + + + + ## Verifying your endpoint Warmbly sends a `POST` to your URL with `X-Warmbly-Event: webhook.test` immediately after creation (and whenever you click **Send test** in the dashboard or call `POST /webhooks/{id}/verify`). The body is: @@ -43,6 +101,10 @@ If you are building an OAuth app that registers endpoints on behalf of users, yo **Changing an endpoint's URL host re-arms verification.** The endpoint goes back to unverified and must pass the challenge again. + + + + ## Selecting events The event picker in Settings shows the full catalog grouped by category. A few things to keep in mind: @@ -53,6 +115,10 @@ The event picker in Settings shows the full catalog grouped by category. A few t The complete event catalog is available programmatically at `GET /webhooks/event-types`. See the [webhooks API reference](/api/reference/webhooks/) for the full list. + + + + ## Reading the delivery log Every delivery attempt is logged. Open an endpoint in Settings -> Webhooks and go to the **Deliveries** tab to see: @@ -66,10 +132,18 @@ You can also query deliveries across all endpoints with `GET /webhooks/deliverie If an event was throttled rather than delivered, it appears in `GET /webhooks/throttle-drops` instead of the delivery log. + + + + ## Redelivering a failed event In the dashboard, open any failed delivery and click **Redeliver**. Programmatically, call `POST /webhooks/deliveries/{deliveryId}/redeliver`. Redelivery re-enqueues the event with its original payload, and the same `X-Warmbly-Event-Id` value, so your idempotency logic will dedupe it correctly if the original delivery eventually arrived. + + + + ## Verifying signatures in your receiver Every delivery carries an `X-Warmbly-Signature` header in the form `t=,v1=`. Verify it against the **raw request body** (before any JSON parsing): @@ -97,6 +171,10 @@ Key points: - Only accept schemes you recognize (`v1`). Ignore any unknown scheme prefix. - Use `X-Warmbly-Event-Id` (or the payload `id` field) as an idempotency key. The same event id may arrive more than once if a retry fires before Warmbly receives your `2xx`. + + + + ## Retries and auto-disable If your endpoint returns a non-`2xx` response (or times out), Warmbly retries with exponential backoff: 30 seconds, 1 minute, 2 minutes, 4 minutes, and so on, capped at 1 hour between attempts, for up to 8 attempts. After 8 failed attempts the delivery is marked abandoned. @@ -105,12 +183,18 @@ Redirects are never followed. `3xx` responses are treated as failures. If an endpoint fails continuously over a sustained window, Warmbly auto-disables it to protect both your downstream systems and any third-party receiving infrastructure. Re-enable it in Settings once the receiver is healthy, then use the delivery log to redeliver any abandoned events you need to recover. + + + + ## OAuth app allowed domains If your OAuth app registers webhook endpoints on behalf of its users, it must declare the allowed domains in `allowed_webhook_domains` when creating or updating the app (see [OAuth](/api/oauth/)). Any endpoint URL your app registers must have a host within that list. An empty list means webhook registration is blocked for the app. Domain matching is subdomain-inclusive when you prefix the entry with a dot: `.acme.com` matches `acme.com`, `hooks.acme.com`, and any deeper subdomain. A bare entry like `acme.com` matches that exact host only. + + ## What comes next - [Webhooks API reference](/api/reference/webhooks/) for the full endpoint, payload, and delivery log spec. diff --git a/docs/content/docs/guides/website-tracking.mdx b/docs/content/docs/guides/website-tracking.mdx index 6ffe029a0..29c51181c 100644 --- a/docs/content/docs/guides/website-tracking.mdx +++ b/docs/content/docs/guides/website-tracking.mdx @@ -3,8 +3,55 @@ title: "Website tracking" description: "Install the tracking snippet, see which pages a contact visits, and control consent and retention." --- +Website tracking adds visits to your own site to a contact's activity timeline: which pages they saw, where they came from and which campaign brought them. It is off until you turn it on. + +## Turn on website tracking + + + + + +Open **Settings > Website tracking** and switch on **Record website visits**. + + + + + +Add the hosts your site runs on under **Your website hosts**. + + + + + +Paste the snippet from the settings page before the closing `` tag on every page. + + + + + +In the default **Ask first** mode, call `warmbly('consent', 'granted')` from your cookie banner once a visitor accepts. + + + + + +## Good to know + +- A visit is tied to a contact only after they click a campaign email link that leads to one of your hosts. +- Visits are kept for 90 days by default, adjustable from 7 to 365. +- The IP address is never kept, and a browser sending Global Privacy Control or Do Not Track is never recorded. +- Visits show in a contact's **Activity** tab under the **Website** filter. + + + +## What website tracking records + Website tracking adds page views on your own site to a contact's activity timeline: which pages they landed on, where they came from, which campaign or UTM brought them, and what device they used. It is off until you turn it on, and the consent mode, location precision and retention window are yours to set. + + + + ## Installation Open **Settings > Website tracking**, switch on **Record website visits**, add the hosts your site runs on, and paste the snippet before the closing `` tag on every page: @@ -28,6 +75,10 @@ The **Your website hosts** list does two things. Page views reported from any ot Leave the list empty and page views from any host are recorded, but none are ever tied to a contact. + + + + ## How a visitor becomes a contact Every link in a campaign email is already a [tracked ticket](/guides/campaigns/) that resolves server-side to the recipient. When a recipient clicks one and the destination is one of your hosts, the redirect appends that ticket to the landing URL as `wbly_t`. The snippet reads it, removes it from the address bar immediately, and sends it with the page view. The server checks that the ticket belongs to a campaign in the same workspace as the site key and, only then, ties the browser to that contact. @@ -42,6 +93,10 @@ If a second contact's ticket arrives on a browser already tied to someone else, On a shared device, call `warmbly('reset')` when it changes hands to drop the visitor and session ids. + + + + ## Consent Choose the mode on the settings page: @@ -51,6 +106,10 @@ Choose the mode on the settings page: The mode is enforced on the server as well as in the snippet, so a stale snippet cannot downgrade a workspace that requires consent. In both modes a browser that sends Global Privacy Control or Do Not Track is never recorded. + + + + ## What is collected | Reported by the browser | Derived on the server | Never kept | @@ -65,16 +124,30 @@ Nothing about the device or location is trusted from the page: the snippet does A session ends after 30 minutes without a page view. The first view of a session is marked as the landing page. + + + + ## Retention **Keep visits for** sets how long page views are kept, from 7 to 365 days (90 by default). A job on the backend prunes each workspace's views past its own window every few hours; the promise on the settings page is what the job keeps. Deleting a contact deletes the browsing history tied to them at the same time. Page views, and the browser records that carry them, travel in the **Delivery events** group of a [workspace archive](/guides/workspace-export-import/). The tracking settings, including the site key, are part of the workspace itself so installed snippets keep reporting after a move. + + + + ## In the timeline Page views show in the **Activity** tab of a contact under the **Website** filter, one line each with the page title or path, the referring domain, the device and any UTM source. Open a row for the full detail: URL, referrer, operating system, browser, resolution, language, timezone, location and every UTM parameter. New views for a contact appear live for everyone with the contact open. + + + + ## Abuse controls The ingest endpoint on the tracking service is public, so it runs behind the same controls as the open pixel and click redirects: a per-source request budget (with a tighter one for page views on top), prefetch and crawler filtering, an 8 KB body cap with limits on every field, a short window that ignores reloads and double-fires, a per-source budget for unknown site keys, and a circuit breaker toward the backend. A forwarded client address is believed only from proxies the operator lists in `TRACKING_TRUSTED_PROXIES`, and only from the one header named in `TRACKING_CLIENT_IP_HEADER`, so a caller cannot choose its own rate-limit bucket or the location stored with a view. Over budget requests get `429`; everything else that is declined is acknowledged quietly so a probe learns nothing about which keys exist. + + diff --git a/docs/content/docs/guides/workspace-export-import.mdx b/docs/content/docs/guides/workspace-export-import.mdx index 7416050a4..316273817 100644 --- a/docs/content/docs/guides/workspace-export-import.mdx +++ b/docs/content/docs/guides/workspace-export-import.mdx @@ -3,6 +3,56 @@ title: "Export and import" description: "Move a whole workspace between Warmbly instances, including mailboxes, campaigns, contacts, and inbox history." --- +A workspace archive is one file holding everything a workspace owns. Use it to move a workspace between Warmbly instances, for example from a self-hosted install to the cloud or back. + +## Move a workspace + + + + + +On the source instance, open **Settings > Data**, pick the groups to export, and select **Start export**. + + + + + +To keep mailboxes connected, turn on **Include mailbox credentials** and choose a passphrase. + + + + + +When the archive lands in the **Archives** list, select **Download**. + + + + + +On the destination, choose the archive file, enter its passphrase, and select **Check this archive**. + + + + + +Read the report, pick the groups to apply, and confirm. + + + + + +## Good to know + +- Export and import are limited to the workspace owner. +- Archives are kept for 7 days and then deleted automatically. +- The passphrase is never stored. If you lose it, the credentials in that archive cannot be recovered. +- An import runs as one transaction. If anything fails, nothing lands. +- To move a whole self-hosted install, use `warmblyctl backup` and `warmblyctl restore` instead. + + + +## About workspace archives + A workspace archive is a single file holding everything one workspace owns. It exists so you can move between instances: a self-hosted install to the cloud, the cloud back to self-hosted, or one self-host to another. Diagnostic participation, shared/rolling limits, restrictive send evidence and audited repair-resolution history travel with their registered mailbox/task/organization data. Source-worker executor handles reset; exact provider-message DKIM authorization/nonces are excluded. Portable minimal receipt proof remains historical evidence, never an execution grant. The reconciliation webhook/notification outbox is source-instance-local and excluded to prevent replay on import; drain pending effects before moving. Whole-instance database backups retain it. See [send-safety retention and rollback](/development/send-safety/#upgrade-rollback-and-portability). @@ -13,6 +63,10 @@ This page moves **one workspace** between two running instances, re-sealing its Everything here lives under **Settings > Data**, and is limited to the workspace owner. An export with credentials contains every mailbox password in the workspace, so it sits at the same level as deleting the workspace. + + + + ## What an archive contains The data is split into groups. Every export includes **Workspace**; the rest are yours to choose. @@ -45,6 +99,10 @@ A restricted member stays restricted on the destination. Their campaign and fold The suppression list is part of the Contacts group and is never optional within it. An import that dropped it would start mailing people who already opted out. + + + + ## Exporting Pick your groups, decide about credentials, and select **Start export**. The archive builds in the background, so you can leave the page; the **Archives** list shows live progress and a **Download** button when it lands. @@ -65,6 +123,10 @@ Leave the option off and the credential fields travel empty. Everything else imp Webhook signing secrets follow the same rule, including the one an [OAuth app](/api/oauth/#receiving-events-as-an-app-webhooks) signs its deliveries with. Without the option, rotate the app's webhook secret on the destination before relying on its deliveries. + + + + ## Importing Choose the archive file, enter its passphrase if it has one, and select **Check this archive**. Nothing is written yet. Warmbly reads the file and reports: @@ -130,6 +192,10 @@ Some things belong to an instance rather than to a workspace, so they are not ap An import runs as one transaction. If anything fails, nothing lands and the workspace is untouched. + + + + ## After a move - **Reconnect any mailbox that needs it.** Mailboxes without credentials show as needing a reconnect in the mailbox list. @@ -150,6 +216,10 @@ An import runs as one transaction. If anything fails, nothing lands and the work If you have shell access to the source, `warmblyctl org export` writes the same archive straight to a file without going through a browser, which is the easier route for a large workspace. See the [warmblyctl reference](/development/warmblyctl/). + + + + ## What does not travel Pending mailbox arrivals and their delivery queue are instance-local. Provider mappings whose arrivals are still pending are excluded too. Let the source consumer finish its pending arrivals before exporting; importing the archive does not replay that source queue. A [full-instance backup](/development/data-control/#mailbox-sync-recovery-data) instead preserves the queue together with its keys and message-body storage. @@ -168,9 +238,15 @@ A [community directory](/guides/integrations/#community-apps) listing does not t Reply and forward drafts kept in your browser are also excluded. Finish or copy them before moving to another instance. See [reply drafts](/guides/unibox/#reply-drafts). + + + + ## Limits - One export and one import can run per workspace at a time. - An uploaded archive can be up to 8 GB. - Archives expire 7 days after they finish. - An archive can be imported into any instance running the same or a newer release. A newer archive on an older instance is refused, with the version in the message. + + diff --git a/docs/public/guides/accounts.webp b/docs/public/guides/accounts.webp new file mode 100644 index 000000000..71aee4b59 Binary files /dev/null and b/docs/public/guides/accounts.webp differ diff --git a/docs/public/guides/add-account.webp b/docs/public/guides/add-account.webp new file mode 100644 index 000000000..1f58a6354 Binary files /dev/null and b/docs/public/guides/add-account.webp differ diff --git a/docs/public/guides/analytics.webp b/docs/public/guides/analytics.webp new file mode 100644 index 000000000..f0143e93a Binary files /dev/null and b/docs/public/guides/analytics.webp differ diff --git a/docs/public/guides/automation-builder.webp b/docs/public/guides/automation-builder.webp new file mode 100644 index 000000000..c36fff6bb Binary files /dev/null and b/docs/public/guides/automation-builder.webp differ diff --git a/docs/public/guides/automations.webp b/docs/public/guides/automations.webp new file mode 100644 index 000000000..f5b2bc559 Binary files /dev/null and b/docs/public/guides/automations.webp differ diff --git a/docs/public/guides/billing.webp b/docs/public/guides/billing.webp new file mode 100644 index 000000000..ec4aa3b0f Binary files /dev/null and b/docs/public/guides/billing.webp differ diff --git a/docs/public/guides/campaign-overview.webp b/docs/public/guides/campaign-overview.webp new file mode 100644 index 000000000..f127661f5 Binary files /dev/null and b/docs/public/guides/campaign-overview.webp differ diff --git a/docs/public/guides/campaign-steps.webp b/docs/public/guides/campaign-steps.webp new file mode 100644 index 000000000..532ba344d Binary files /dev/null and b/docs/public/guides/campaign-steps.webp differ diff --git a/docs/public/guides/campaigns.webp b/docs/public/guides/campaigns.webp new file mode 100644 index 000000000..b2c216b4d Binary files /dev/null and b/docs/public/guides/campaigns.webp differ diff --git a/docs/public/guides/contacts.webp b/docs/public/guides/contacts.webp new file mode 100644 index 000000000..5f205adc9 Binary files /dev/null and b/docs/public/guides/contacts.webp differ diff --git a/docs/public/guides/deals.webp b/docs/public/guides/deals.webp new file mode 100644 index 000000000..46224761f Binary files /dev/null and b/docs/public/guides/deals.webp differ diff --git a/docs/public/guides/deliverability.webp b/docs/public/guides/deliverability.webp new file mode 100644 index 000000000..5e9f29dbc Binary files /dev/null and b/docs/public/guides/deliverability.webp differ diff --git a/docs/public/guides/mailbox-warmup.webp b/docs/public/guides/mailbox-warmup.webp new file mode 100644 index 000000000..680f4990f Binary files /dev/null and b/docs/public/guides/mailbox-warmup.webp differ diff --git a/docs/public/guides/sending-domains.webp b/docs/public/guides/sending-domains.webp new file mode 100644 index 000000000..da4dfaffe Binary files /dev/null and b/docs/public/guides/sending-domains.webp differ diff --git a/docs/public/guides/unibox.webp b/docs/public/guides/unibox.webp new file mode 100644 index 000000000..8b4ebbda7 Binary files /dev/null and b/docs/public/guides/unibox.webp differ