# Odoo Prospect Handoff

> Import verified prospects into Odoo, prepare personalized email campaigns and sequences, apply approved contact tags, process reported campaign responses, and maintain traceable CRM/contact records. Use after prospect research or for a campaign handoff; the final send, test-send, schedule, or reply action is always human-only.

- Skill: `efedkaya/odoo-prospect-handoff` (Agent Skill, multi-file: 10 files)
- Install (CLI): `npx skillmds@latest add efedkaya/odoo-prospect-handoff`
- Raw SKILL.md: https://api.skillmd.com/api/skills/efedkaya/odoo-prospect-handoff/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: efedkaya (https://skillmd.com/u/efedkaya)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/efedkaya/odoo-prospect-handoff

---


# Odoo Prospect Handoff

## Purpose

Move completed prospect research into Odoo without duplicating companies or people, overwriting better data, or accidentally starting outreach. Adapt each run to the user's requested Odoo objects, fields, assignments, and outreach setup rather than requiring a fixed campaign structure.

This skill is the handoff stage after company research and professional-contact enrichment. It may create or update approved Contacts and CRM records, prepare mailing lists and personalized email sequences, and audit human-sent outreach on the matched records. It does not research missing contacts, decide who should be targeted, infer consent, or trigger the final send, test-send, or schedule action.

## Operating Modes

- **Preview:** inspect the source and Odoo, discover the live schema, propose mappings, and report creates, updates, skips, and ambiguities without writing. Use this by default for a new database, new source layout, or changed mapping.
- **Import:** perform only the approved creates and updates, in checkpoints, after preview or when an established mapping and scope are already clear.
- **Continue:** resume from the saved checkpoint without repeating completed records.
- **QA:** verify an earlier import against the source and Odoo without changing records unless correction is explicitly requested.
- **Outreach preparation:** create only the specifically requested activities, mailing contacts, mailing-list memberships, or other draft structures. Preparation is not authorization to send, schedule, launch, subscribe an unapproved person, or remove an opt-out.
- **Campaign preparation and post-send audit:** collect the campaign brief, prepare recipients, tags, CRM records, mailing lists, and personalized drafts, then hand the final send action to a human. After the human sends, verify Odoo's status and record the event on confidently matched Contacts and approved CRM records.
- **Response follow-up:** when the user reports a recipient response, match it to the exact Contact and campaign, record the supported outcome, update an already-linked or explicitly approved CRM record, and prepare the next action. Never assume qualification or send a reply.

For campaign strategy, the Five Whys intake, sequence drafting, bulk recipient/tag preparation, human-send handoff, post-send logging, and response handling, read [references/campaign-workflow.md](references/campaign-workflow.md) completely before acting.

For an established organization-specific Odoo instance, read the private instance profile at `~/.codex/private/odoo-prospect-handoff/instance-profile.md` when it exists. For a new organization or deployment, read [references/instance-profile-template.md](references/instance-profile-template.md) and establish only the details needed for the requested run. Treat every instance profile as a navigation and mapping accelerator, not as proof that the live schema is unchanged. Keep real organization names, domains, instance URLs, users, credentials, and local operating conventions out of the portable skill and public repository.

For a multi-row Contacts import—especially one containing repeated people, several contacts per company, workbook colour/status conventions, or possible older Odoo duplicates—read [references/contact-import.md](references/contact-import.md) completely before Preview or Import.

## Resolve Only the Inputs Needed for This Run

Infer established details from the workbook, Odoo, and prior instructions. Ask only for unresolved choices that materially affect the requested action.

Depending on scope, resolve:

- Source workbook or structured data, working sheet, row range, and stable source identifiers.
- Available Odoo connection and database.
- Requested Odoo objects: companies, individual contacts, leads, opportunities, activities, mailing contacts, or mailing-list membership.
- Field mapping for the requested objects.
- Duplicate-match and update policy when the defaults below are not appropriate.
- Optional salesperson, sales team, stage, tags, source, campaign, activity type, due date, or mailing list.
- Whether missing Odoo fields should remain in notes, be omitted, or be added as custom fields by an authorized administrator. Never create custom fields by assumption.
- Explicit authorization for the current write scope. A read-only inspection or preview is not write authorization.
- The human responsible for the final send, test-send, or schedule action and the confirmation they will provide afterward for status verification and audit logging. Import authorization never authorizes the skill to transmit outreach.

Do not require email, campaign, activity, stage, owner, tags, or mailing setup when they are not part of the user's requested run.

## Connection and Schema Discovery

- Keep the Odoo URL, database, login, and API key in environment variables or another approved local secret mechanism. Never place credentials in this skill, a workbook, a generated CSV, logs, or the completion report.
- Before the first write in a session, perform a read-only authentication check.
- Discover the current model fields with `fields_get` for every model in scope. Odoo installations and customizations differ; never assume a field exists because another database has it.
- For a recognized instance profile, reuse its verified navigation and candidate mappings during Preview. Perform a lightweight drift check of the fields and views in scope, and still run `fields_get` before the first write of the session. Cache that result for the session instead of rediscovering the same model for every row.
- Check visible database-health warnings and relevant module availability before relying on imports or long-running automation. Report transient warnings; do not encode their current values as permanent skill facts.
- Use the JSON-2 helper for deterministic API operations when Odoo JSON-2 is available. Read [references/odoo-json2.md](references/odoo-json2.md) before using it.
- Prefer API-based preview, matching, writes, and read-back for a large Contacts batch. Use the visible Odoo interface for authentication, unavailable API operations, and small human spot checks; do not use mass export as the default way to inspect a large contact database.
- Use Odoo's visible import interface when JSON-2 is unavailable or when the user explicitly chooses CSV/UI import. Preserve stable External IDs in update-compatible imports.
- Never inspect private network traffic, extract browser session tokens, or bypass Odoo access rights.

## Default Object Mapping

Follow the user's approved mapping when provided. Otherwise propose these mappings in Preview mode and write only supported fields.

### Company contact (`res.partner`)

| Source concept | Odoo destination |
|---|---|
| Company name | `name` with company type enabled |
| Website/domain | `website` |
| Headquarters | Structured address fields when the source supports each component |
| Main company phone | `phone` when requested |
| Source identifier | Existing approved custom field or import External ID |
| Auditor, audit fee, market cap, factual flags, source dates | Approved custom fields; otherwise concise internal notes only when requested |

Do not put a person's email or phone on the company record merely because it is the only contact value available.

### Individual contact (`res.partner`)

| Source concept | Odoo destination |
|---|---|
| Executive name | `name` |
| Company | `parent_id` linked to the resolved company |
| Role/title | `function` |
| Work email | `email` |
| Professional phone | `phone` or `mobile` according to the supported type and approved mapping |
| LinkedIn URL | Approved custom field; otherwise internal note only when requested |
| Seniority | Approved custom field; otherwise internal note |
| Source workbook notes | Internal note, separated from generated provenance |
| Source, status, and verification date | Approved custom fields or concise internal note |

Never place a personal email in a work-email field. Preserve phone country codes and store phone values as text in intermediate files.

### Keep Odoo note surfaces distinct

- **Contact Notes tab:** persistent profile metadata that lacks an approved structured field, such as LinkedIn, seniority, source provenance, verification status, and source-workbook notes.
- **Contact chatter / Log note:** timestamped operational history, such as an approved import, campaign enrollment, or verified campaign event. Do not copy all profile metadata into chatter.
- **CRM Notes tab:** opportunity-specific context. Do not use it as the only home for reusable person or company facts.

Use stable labels and preserve unrelated existing note content. Append or update only the approved block; do not replace the whole note field by default.

### CRM lead or opportunity (`crm.lead`)

Create one only when requested. Resolve whether the record is a lead or opportunity and map the approved company, contact, owner, team, stage, tags, source, campaign, priority, and description. Do not infer a priority, expected revenue, probability, close date, or qualification decision.

### Activities and mailing records

Create activities only when the activity type, responsible user, summary, and due-date rule are clear or intentionally left to Odoo defaults. Create mailing contacts or mailing-list membership only when explicitly requested and the person's outreach eligibility is supported.

## Matching and Deduplication

Search before every create. Prefer stable identifiers and exact supported evidence over names alone.

For a batch, preload candidate companies and people with a small number of bounded searches using the batch's exact stable identifiers, work emails, domains, and LinkedIn URLs. Classify matches from that candidate set, then recheck the exact stable evidence immediately before each create. This reduces round trips without weakening duplicate protection.

### Companies

Use the strongest available evidence in this order:

1. Existing approved source-ID field or import External ID.
2. Exact normalized website domain.
3. Exact tax/VAT identifier when the source and destination meaning match.
4. Exact normalized company name plus jurisdiction or address evidence.

### People

Use the strongest available evidence in this order:

1. Existing approved source-ID field or import External ID.
2. Exact work email.
3. Exact LinkedIn URL in an approved field.
4. Exact normalized full name plus resolved parent company and compatible role.

### Leads or opportunities

Prefer an approved source/campaign identifier or import External ID. Otherwise use the resolved company, contact, record type, and campaign/source context. A similar title alone is not a duplicate key.

### Match outcomes

- **Confident existing match:** update only approved fields.
- **No existing match:** create only when the row is in the approved create scope.
- **Ambiguous match:** do not create or update; flag for human review.
- **Conflicting stable identifier:** stop that record and report the conflict.

When several source rows resolve to one person, retain every source-row reference but create or update only one canonical Odoo person. When the same stable person appears under different companies, do not silently choose a parent: apply the evidence and exception rules in the contact-import reference.

Never automatically merge contacts. Never treat a blank source value as an instruction to clear Odoo. Never overwrite a populated Odoo value with blank, stale, personal, or lower-confidence data. Preserve manually maintained Odoo values unless the user approved that field as source-controlled.

## Preview Procedure

1. Inspect headers, representative rows, formulas, and existing import/status columns without changing the source. For `.xlsx` prospect lists, prefer `scripts/prepare_prospects.py` to produce normalized, row-addressable preview data and warnings. For repeated contact rows, pass that JSON through `scripts/group_contacts.py` to consolidate people while preserving source-row lineage and flagging cross-company conflicts. State whether row numbers mean worksheet rows or data-record positions.
2. Confirm the requested row and object scope.
3. Authenticate read-only and discover fields for the in-scope Odoo models.
4. Resolve referenced Odoo values such as users, teams, stages, tags, activity types, campaigns, and mailing lists by ID. Do not create configuration records unless requested.
5. Build a row-level plan containing the source row or stable ID, intended Odoo objects, match evidence, proposed action, and exception status.
6. Report aggregate counts for creates, updates, skips, ambiguous matches, missing required values, and unavailable mappings.
7. Show representative mapping decisions without exposing the API key or unnecessarily repeating contact details.

For a new mapping, recommend a small approved test batch before a full import.

### Human review and execution loop

1. Present the row-level preview, counts, mappings, and exceptions in chat.
2. Obtain approval for an exact source, sheet, row scope, mapping, and write scope.
3. Apply only that approved scope in Odoo.
4. Read back critical fields and reconcile counts.
5. Provide the resulting Odoo record IDs or open the records for a user-requested spot check without exposing unnecessary contact data in chat.

Review in chat is the default decision point. Odoo becomes the system of record only after the approved write and read-back verification.

## Import Procedure

1. Confirm the approved preview, scope, mapping, update policy, and allowed object types.
2. Recheck authentication and the expected source identity before writing.
3. Resolve or create the company first, then link people, then create the requested CRM and outreach-preparation records.
4. Process in source order and checkpoints of 3–5 companies unless the user requests another interval.
5. After every create or update, capture the returned Odoo record ID and read back the critical fields.
6. Save a checkpoint containing source location, sheet, last completed row or stable ID, created/updated Odoo IDs, access date, and unresolved exceptions. Do not store credentials in the checkpoint.
7. On resume, verify the checkpoint's source identity and Odoo IDs before continuing.

For a reviewed plan with fully resolved `res.partner` create/update actions, `scripts/import_contacts.py` provides guarded dry-run, resumable execution, and per-record read-back. It intentionally cannot archive, merge, delete, create CRM objects, or send anything. Matching and any archive allowlist must be reviewed before producing its plan.

For a live workbook status column, write an Odoo result only after successful read-back. Map the same canonical Odoo ID to every source row consolidated into that person. Use explicit results such as `Created`, `Updated`, `Skipped - ambiguous`, or `Failed validation`; never mark an unverified row imported.

Do not retry a failed write blindly. First determine whether the original request committed by searching for the stable source identity; then continue idempotently.

## Outreach Rules

- Contact enrichment does not establish consent or outreach eligibility.
- Treat CRM/Contacts records and Email Marketing mailing contacts as separate layers. A valid `res.partner` record is not automatically an eligible `mailing.contact`.
- Before mailing-list preparation, reconcile the exact email, duplicate/shared addresses, list membership, bounce history, opt-out state, blacklist state, and the user's approved eligibility basis. Exclude unresolved or suppressed addresses.
- Preserve existing opt-outs, blacklists, exclusions, and do-not-contact indicators. Never unblacklist or remove an opt-out.
- Do not subscribe a person to a mailing list unless the user explicitly requests that list membership and the approved data provides a valid basis/status for doing so.
- Prepare approved outreach up to the final transmission step, but never click or trigger a final **Send**, **Send to all**, **Test**, **Schedule**, launch, or equivalent action. The human must perform the action that transmits or schedules the communication.
- A request to create activities means internal follow-up tasks, not permission to contact the person.
- Keep message content and sequence rules user-defined. Do not invent outreach copy, timing, sender accounts, or targeting criteria unless the user requests drafting or planning.

### Contact and CRM outreach audit trail

Follow the campaign workflow reference for enrollment, queue, send, and reported-response logging. At minimum, write an internal chatter note on each confidently matched individual Contacts record and on its linked lead or opportunity when the approved CRM policy created or selected one. Use verified Odoo states or user-supplied reply evidence, let Odoo supply the timestamp, prevent duplicate notes, and never create CRM records merely to satisfy logging unless the user selected that CRM policy.

## Validation

For each imported company:

- The source row or stable ID still matches the intended company.
- The Odoo company match is supported and no stronger duplicate candidate exists.
- Every person is linked to the correct parent company.
- Work email, phone type, title, LinkedIn, source, and verification date retain their supported meaning.
- No blank source field cleared a populated destination.
- Requested lead/opportunity type, owner, team, stage, tags, and source are correct.
- Activities and mailing records exist only when approved.
- Existing blacklists and opt-outs remain unchanged.

After each checkpoint, verify one representative company and the last company written. At completion, compare expected and actual create/update/skip counts and run a duplicate review on the imported scope.

## Error Handling

- **Authentication or access error:** stop writes, preserve the checkpoint, and report the missing permission without exposing credentials.
- **Schema changed:** rediscover fields and return to Preview mode when the mapping is no longer unambiguous.
- **Ambiguous duplicate:** skip that object and flag it for human review.
- **Inactive record not visible:** repeat the read with `active_test` disabled before concluding that an archived record is missing.
- **Partial company import:** verify which objects committed before retrying; continue only the missing actions.
- **Validation mismatch:** stop the batch, preserve affected IDs, and do not expand the scope.
- **Rate limit or outage:** stop cleanly and report the exact next source row or stable ID.
- **Mailing blacklist or opt-out:** skip mailing membership or outreach preparation for that address and preserve the exclusion.

## The AI Must Never Invent or Assume

- A company/person match, source identifier, field mapping, Odoo record ID, or parent relationship.
- Consent, lawful basis, outreach eligibility, or permission to subscribe or send.
- A salesperson, team, stage, tag, source, campaign, activity, deadline, priority, revenue, or probability.
- Permission to create custom fields, merge, delete, archive, unblacklist, clear populated values, or alter Odoo configuration.
- That successful HTTP delivery means the data is correct; verify returned and read-back values.

## Completion Report

Return a compact report containing:

- Odoo database label, source workbook/sheet, and row scope.
- Requested object types and mapping used.
- Companies, people, leads/opportunities, activities, and mailing records created or updated.
- Skips, ambiguous duplicates, unavailable fields, and validation exceptions.
- Exact next row or stable ID when incomplete.
- Confirmation that credentials were not written into outputs.
- Confirmation that no unapproved merge, deletion, subscription, send, schedule, or configuration change occurred.
- Final status: `Import verified`, `Ready for approved import`, or `Needs human review`.

Do not repeat row-level contact data in the chat report unless the user explicitly requests it and is authorized to receive it.

