I've been working on a project to clean up our CRM data, specifically targeting the accumulation of stale contact records that inflate storage costs and can negatively impact machine learning model training for features like lead scoring. The requirement was to archive contacts with zero engagement (no opened emails, no website visits, no filled forms) for over 18 months. While the CRM's UI allows batch actions, the limit is 100 records at a time, which is impractical for an initial cleanup of several hundred thousand records.
I developed a Python script that leverages the Gemini API to not only identify these contacts but also to generate a brief, natural-language summary of why they were archived (e.g., "Last activity: Form submission in Q3 2022") before moving them to a dedicated archive segment. The script uses a two-pass approach for safety and logging.
Here is the core script structure:
```python
import google.generativeai as genai
import crm_api_client # Hypothetical client for your CRM
import pandas as pd
from datetime import datetime, timedelta
# Configuration
genai.configure(api_key='YOUR_API_KEY')
model = genai.GenerativeModel('gemini-1.5-flash')
THRESHOLD_DAYS = 540
ARCHIVE_SEGMENT_ID = 'segment_archive_123'
def generate_archive_reason(contact_activity_log):
"""Uses Gemini to create a concise, human-readable reason for archiving."""
prompt = f"""
Based on the following last activity log for a contact, create a single, short sentence summarizing their last engagement for an archive note.
Use only the data provided. Format: 'Last activity: [activity] in [Month Quarter Year]'.
Activity log: {contact_activity_log}
"""
response = model.generate_content(prompt)
return response.text.strip()
def main():
# 1. Fetch candidate contacts from CRM API (simplified)
all_contacts = crm_api_client.get_contacts(fields=['id', 'last_engaged', 'activity_log'])
cutoff_date = datetime.now() - timedelta(days=THRESHOLD_DAYS)
to_archive = []
for contact in all_contacts:
if contact['last_engaged'] and contact['last_engaged'] < cutoff_date:
to_archive.append(contact)
# 2. Generate archive reasons and prepare batch payload
archive_batch = []
for contact in to_archive:
reason = generate_archive_reason(contact.get('activity_log', 'No recorded activity'))
archive_batch.append({
'contact_id': contact['id'],
'archive_reason': reason,
'action': 'add_to_segment'
})
# 3. Dry-run: output analysis without taking action
print(f"Identified {len(archive_batch)} contacts for archiving.")
df = pd.DataFrame(archive_batch)
print(df['archive_reason'].value_counts().head())
# 4. (Optional) Uncomment to execute the archive via CRM API
# for batch in chunk(archive_batch, 100): # Respecting API rate limits
# crm_api_client.batch_update({
# 'segment_id': ARCHIVE_SEGMENT_ID,
# 'contacts': batch,
# 'note': batch['archive_reason']
# })
# log_archive_operation(batch)
if __name__ == '__main__':
main()
```
**Key Observations & Performance:**
* **Gemini Model Choice:** I used `gemini-1.5-flash` for this task. The latency for generating hundreds of short, structured reasons is significantly lower than `gemini-1.5-pro`, and the quality is perfectly adequate for this operational, non-customer-facing text. The cost per thousand calls is negligible.
* **Prompt Engineering:** The prompt is constrained to output a very specific format. In testing, I found that without the explicit format instruction, the model would occasionally add explanatory clauses, which broke the uniform note structure required for our system log.
* **Error Handling:** In the production version, I wrapped the `generate_content` call in a retry logic with exponential backoff, specifically handling `429` (rate limit) and `500` errors from the API. The Gemini API's reliability has been solid, but this is essential for batch jobs.
* **Cost & Speed:** Processing 10,000 contacts through the `flash` model took approximately 4 minutes and cost under $0.10. A manual review and note-entry process for the same volume would be economically unfeasible.
**Potential Pitfalls to Consider:**
* **Data Freshness:** The script's efficacy is entirely dependent on the quality and completeness of the `last_engaged` and `activity_log` fields from the CRM. Inconsistent data logging will lead to false positives or negatives.
* **CRM API Limitations:** The hypothetical `crm_api_client.batch_update` must be implemented according to your CRM's actual bulk API, which often has payload size limits and required intervals between calls. The script's chunking logic is critical.
* **Audit Trail:** It is imperative to log the `contact_id` and the generated `archive_reason` to an immutable audit log before executing the archive action, in case a restore is needed.
This approach has successfully automated a previously manual and error-prone hygiene task. The next iteration will likely involve using the Gemini API to categorize the *type* of inactivity (e.g., "marketing email non-responder" vs. "trial user no adoption") to route contacts to different re-engagement workflows before final archiving.
Data over dogma