DocsAutoResponderProcess

AutoResponder Service Status Tracker#

Last verified against code: see repository
Snapshot template: When sharing a run-specific status, set: WRITE_OPERATIONS_ENABLED=…, MOCK_ALL_SERVICES_FOR_DEV=…, DRY_RUN_USE_REAL_EMAIL_REPOSITORY=…, EMAIL_PULL_START_DATE=…, EMAIL_PULL_END_DATE=…, CUPOLA_AUTOMATIC_UPDATES=….

This document summarizes how each integration behaves by operating mode. It is not a substitute for connections/README.html, README.html (documentation index), or main.py; refresh it when wiring changes.

Active-only policy. The action engine now gates every automated write on an active CUPOLA row. ACTIVE determinations never auto-add or auto-reactivate; EMAIL_UPDATE / TITLE_UPDATE only fire when the preferred CUPOLA row exists and is active; OUT_OF_OFFICE is a first-class determination that writes nothing to any backend. The per-connector "LIVE" columns below describe what happens when the gate passes; otherwise the row is routed to the Human Review digest (see docs/OutputDocument.html).


Operating Modes#

ModeEmail repoDB connectorsLLMSFMCNotifier
Live (WRITE_OPERATIONS_ENABLED=true, MOCK_ALL_SERVICES_FOR_DEV=false)Live MSSQLLive (with mock fallback where configured)LiveLive (mock fallback if unhealthy)Live
Simulation (no writes) (WRITE_OPERATIONS_ENABLED=false)Live MSSQLLive reads; writes intercepted via ReadOnlyDatabaseWrapper where wrappedLiveMock clientDry-run (logged, not sent)
Dev mock-all (MOCK_ALL_SERVICES_FOR_DEV=true)Mock by default; optional live inbox if dry_run_use_real_email_repository=trueCupola / Multipub / Salesforce: mocks. Hodor: real HodorDatabase (graceful-unavailable if unset). SFMC: mock.LiveMockDry-run

Read-only: Cupola, Multipub, Salesforce, and Hodor (when SQL is configured and available) use ReadOnlyDatabaseWrapper for writes. SFMC uses a mock client.

Hodor business behavior: With Hodor SQL configured, lookups still hit the database; ActionEngine emits Prospect Import rows and sends them via notify_hodor_prospect_import to NOTIFICATION_EMAIL_VENU (catalog N01). Details: connections/hodor.html.


Service-by-Service Status#

1. Email Repository#

OperationStatusNotes
get_emails()Live in non–dry-run and in dry-run when dry_run_use_real_email_repository=trueReads CBI_EmailExtraction / emails (see connections/email-repository.html)
Mock in dry-run when dry_run_use_real_email_repository=false (default)Fixture data from MockEmailRepository

2. LLM Classification (Determination Service)#

OperationStatusNotes
classify_email()LIVE (always)Real OpenAI/Ollama calls in all modes
qa_check()LIVE (always)
validate_email()LIVE (always)
identify_auto_response_source_email()LIVE (always)
map_category_to_determination()LIVE (always)Pure mapping logic, no external calls

3. Cupola Database#

OperationRead-onlyLive ModeDry-RunNotes
lookup_by_email()LIVE readLIVEMocked
update_contact_status()InterceptedLIVE only when _handle_inactive passes the active-with-org gate and CUPOLA_AUTOMATIC_UPDATES=true. Never called for reactivation; inactive CUPOLA rows on ACTIVE determinations route to Human Review.MockedWrapper around update_contact_status_with_audit.
update_contact_status_with_audit()Intercepted (returns (True, None) — previous status cannot be observed)LIVE when invoked by _handle_inactive. Uses OUTPUT deleted.status to capture previous_status atomically, which CupolaAuditLogger records and uses to write cupola_audit_log_rollback_plan.csv.Mocked (returns (True, 1))The audited entry point used by the action engine.
check_active_subscription()LIVE readLIVEMocked
add_contact()InterceptedLIVE only from REPLACEMENT flow (cupola_auto_add_replacements=true). Never called from _handle_active.MockedSee CUPOLA_UPDATE_STATEMENTS.html §5.
update_contact_email()InterceptedLIVE only when _handle_title_update passes the active-only CUPOLA gate.Mocked
update_contact_title()InterceptedLIVE only when _handle_title_update passes the active-only CUPOLA gate.Mocked
Other readsLIVELIVEMocked

4. Multipub Database#

OperationRead-onlyLive ModeDry-RunNotes
ReadsLIVELIVEMocked
Writes (update_*, add_contact, etc.)InterceptedLIVEMocked

5. Hodor Database (SQL Server)#

Direct pyodbc access to the prospect table (see HODOR_DATABASE_SCHEMA.html). Not browser automation.

OperationRead-onlyLive ModeDry-RunNotes
lookup_by_email() / lookup_batch()LIVE read when configuredLIVEReal connector; contact lookup step is skipped in dry-run (see §10), but HodorDatabase is still constructedGraceful-unavailable if HODOR_* / HODOR_CONNECTION_STRING unset
update_contact_status(), update_contact_email(), update_contact_title()Intercepted when wrapped (available)Connector methods exist, but business handlers emit Prospect Import rows instead of invoking these writesNot wrapped (dry-run uses mocks for other DBs; Hodor is real class — see main.py)
add_contact()Logged / no-op per connectorNot fully implemented
check_active_subscription()LIVE read when implementedLIVE

6. Salesforce#

OperationRead-onlyLive ModeDry-RunNotes
lookup_by_email()LIVE read (or mock fallback)LIVEMocked
WritesIntercepted (if real connector)LIVEMocked

7. SFMC (Salesforce Marketing Cloud)#

OperationRead-onlyLive ModeDry-RunNotes
add_to_suppression_list()MockedLIVE (mock fallback)Mocked
check_health()MockedLIVEMocked

8. Notifier#

OperationRead-onlyLive ModeDry-Run
notify_multipub_subscriber_followup_batchDry-run (logged, not sent)LIVE — Angel + Yogesh To, Matt Cc (+ global Max + Vish)Dry-run
notify_tarun_undetermined_sender_reviewSameLIVE when undetermined-sender queue non-emptySame
notify_sai_action_itemsSameLIVE when manual-review bundle non-emptySame
notify_hodor_prospect_importSameLIVE when queued Hodor Prospect Import rows existSame
notify_run_audit_for_ip4SameLIVE every run — To: NOTIFICATION_EMAIL_SAISame
notify_venu_cupola_audit_filesSameLIVE every run — Venu (+ Sai Cc, Max for HR metadata)Same
notify_marketing_suppressionSameLIVE when suppression rows exist — To: marketing team mailbox (NOTIFICATION_EMAIL_ERINmarketing-team-cbi@columbiabooks.com)Same
notify_multipub_subscriber_followup_from_uploadSameLIVE on Tarun upload YesSame
_send_notification_emailSameGlobal Max + Vish Cc on operational sendsSame

9. Action Engine#

HandlerRead-onlyLive ModeDry-RunNotes
_handle_inactiveLIVE logic; connector writes intercepted by wrapperLIVE — CUPOLA flip subject to active-with-org gate + CUPOLA_AUTOMATIC_UPDATES; Hodor emits Prospect Import rows; Salesforce follows skip rulesMocked connectors
_handle_activeLIVE logic; connector writes interceptedLIVE — no auto-add, no auto-reactivate. No-CUPOLA rows route to IP4 handoff + Human Review. Inactive-CUPOLA rows route to Human Review as reactivation candidates. Hodor / non-Multipub Salesforce mirror reactivation still runs.Mocked connectorsNo-CUPOLA / reactivation rows queue for notify_sai_action_items (catalog N05/N06).
_handle_title_update (EMAIL_UPDATE / TITLE_UPDATE)LIVE logic; connector writes interceptedLIVE — gated on active CUPOLA row. Inactive or missing CUPOLA blocks the update on every system and routes to Human Review.Mocked connectors
_handle_replacementLIVE logic; connector writes interceptedLIVE — marks original contact inactive on CUPOLA (audited); replacement row written to output_document_alternate_contacts.csv. add_contact only fires when CUPOLA_AUTO_ADD_REPLACEMENTS=true.Mocked connectors
_handle_out_of_officeLIVE logicLIVE — no backend writes. Emits Human Review row with HUMAN_REVIEW_REASON_OUT_OF_OFFICE.Mocked connectorsOOO is tracked as its own workflow; no CUPOLA / Hodor / Multipub / Salesforce calls.
Hodor updates from any handlerIntercepted via wrapper when WRITE_OPERATIONS_ENABLED=falseRouted to Prospect Import rows + notify_hodor_prospect_import (§8)Mocked / graceful-unavailable

10. Contact Lookup#

OperationRead-onlyLive ModeDry-RunNotes
lookup_with_enrichment() / batchLIVELIVESkippedmain.py builds a minimal Contact with empty recordsAvoids meaningless mock data during dry-run

Read-Only Wrapper Behavior#

When WRITE_OPERATIONS_ENABLED=false, ReadOnlyDatabaseWrapper (src/auto_responder/services/databases/readonly_wrapper.py) intercepts listed write operations for Cupola, Multipub, Salesforce, and Hodor (when the real connector is available).

Intercepted methodBehavior
update_contact_status()Logged to action log, not executed
update_contact_status_with_audit()Logged to action log, returns (True, None)previous_status cannot be observed in read-only mode
execute_mark_person_inactive_sp()Logged to action log, not executed
execute_org_person_updates_sp()Logged to action log, not executed
add_contact()Logged, returns mock ID, not executed
update_contact_email()Logged, not executed
update_contact_title()Logged, not executed
update_lead_status()Logged, not executed

Reads pass through to the live database.

Note: Hodor update intents are emitted as Prospect Import rows at the application layer. WRITE_OPERATIONS_ENABLED=false still intercepts connector writes if any direct call path is introduced later. See connections/hodor.html.