Autonomous outbound sales engine for an industrial manufacturer
The agent deploys on top of a live corporate mailbox, reconstructs the manager's history and writing style, and brings back into play a customer base the company had written off as lost.
Context
In the conservative B2B market for construction composites, sales rest on specific people. A manager spends years corresponding with developers, contractors, and procurement staff — and all of that knowledge lives in a mailbox. A formal CRM often does not exist: the mailbox is the CRM, unreadable to the business. When the manager drops out, the customer base, deal history, and tone stay locked in correspondence. And the work happens on a live corporate mailbox, where a sending mistake is instantly visible to real customers — automation is acceptable only inside a strict approval workflow.
The task
The brief was to seat an agent directly in a sales manager's working mailbox: connect over IMAP, reconstruct the manager's work, mine the customer base and price lists from the correspondence, then reactivate dormant relationships — in the tone and under the signature of a specific person. The product line is narrow and technical — composite and fiberglass rebar, mesh, fiber — so the agent had to name the line correctly and hit the right price tier for each order volume.
Approach
The email transport is pure Python standard library — imaplib and smtplib, zero external dependencies — a deliberate choice for a tool on live corporate infrastructure. Credentials live in .env; mailbox access uses an app password.
Stage one was reconstruction: the system surveyed a mailbox of 14,754 incoming emails and rebuilt the manager's working picture — correspondents, cadence, signature — collecting style samples so the generators write like this specific person. Stage two was data: structured CSVs of inbound customers and 898 real correspondence contacts — the company's de facto CRM — plus the client's PDF price lists parsed into a JSON catalog for quote calculation.
Architecture
The live environment bit immediately: the Sent folder was empty — all outgoing mail sat in the INBOX — so 834+ outgoing emails were extracted in a separate FROM-self pass to reconstruct history, tone, and signature. The system is deliberately a set of single-responsibility scripts rather than a monolith — connection check, mailbox survey, contact mining, style export, email and quote generators, a guarded sender — each verified independently.
The price catalog is a multi-tier grid — three tiers for rebar, two for mesh — and quote.py selects the tier from the order volume on its own. The second source was 1C:UNF: with OData closed by restricted permissions, the base was obtained via UI export to xlsx and parsed with scripts — 3,712 contacts, segmented into 1,242 with email, 2,892 with phone, 1,203 with both channels.
Approval workflow
send.py defaults to dry-run: nothing goes out without an explicit --send flag, and even then only emails from an explicit APPROVED list, with every send logged — one-click mass mailing does not exist by design. Approval runs on an interactive HTML board: a checkbox per email, previews, automatic supplier/competitor and duplicate-domain flags, one-click copy of the approved list. The reports record a key caveat: the base is mostly procurement staff and engineers — an entry point, not final decision-makers — and the reactivation strategy is built accordingly.
Outcome
By handover the engine had reconstructed the manager's history from 14,754 incoming emails, consolidated a working CRM of 898 correspondence contacts, and extended it with 3,712 contacts from 1C:UNF — the company now holds its customer base as a manageable, segmented asset. The quote generator prices orders against the real catalog, and drafts are queued: 10 first-wave reactivation emails and a mass wave of 85 emails across 69 companies. Response and conversion will be measured from the first wave's actuals; the engineering result — a repeatable model for recovering commercial expertise from a mailbox — is already in hand.
What we built
Connecting to the live mailbox
An IMAP/SMTP connector in pure Python stdlib on top of the working mailbox; credentials live in .env and are never committed.
History and style reconstruction
The manager's outgoing emails extracted straight from the INBOX keyed on FROM self (834+); tone and signature reconstructed from the bodies for the generators.
Mining the customer base
CSVs assembled from the mailbox: inbound customers, 898 real correspondence contacts (the CRM), and a list of warm repeat buyers for the pilot.
Price catalog from PDF
The supplied price lists parsed into JSON with a three-tier grid for rebar and a two-tier grid for mesh; the quote generator runs on this catalog.
Email and quote generators
Scripts assemble reactivation emails and the mass wave (85 emails / 69 companies) with name substitution from a dictionary; quote.py calculates quotes for the order volume.
Second source: 1C:UNF
With OData closed, the database was obtained via a UI export to xlsx and parsed with scripts: 3,712 contacts, phone and address normalization, channel segments.
Approval board
Interactive HTML with checkboxes, email previews, "supplier/competitor" and "duplicate domain" flags, and one-click copying of the approved list.
Guarded sending
send.py defaults to dry-run, actually sends only emails from the APPROVED list with the --send flag, and keeps a log; a human in the loop is mandatory.
Engineering challenges
Outgoing mail not in Sent
The Sent folder was empty — all of the manager's outgoing correspondence sat directly in the INBOX. Emails were extracted in a separate pass keyed on FROM self, yielding 834+ samples for reconstructing history and style.
mail.ru TLS throttling
Under frequent connections mail.ru blocks TLS on ports 993 and 465. We split the work into sessions with pauses between them and send only when the block is lifted.
Closed OData in 1C
OData was enabled but the interface composition was empty due to restricted permissions. Rather than wait for an administrator, we obtained the database through the UI — filtering by responsible manager and exporting to xlsx — and parsed it with scripts: 3,712 contacts versus 898 from mail.
Sending to live customers
A mailing mistake is visible to real customers, so automatic mass sending is ruled out by design: dry-run by default, an explicit APPROVED list, a log of sent emails, and mandatory manual approval through the board.