If you run a web service or app solo, you already know the feeling: a batch of contact form submissions hits your inbox, and half your morning disappears sorting through them.
Is this a bug? A billing question? A feature request that's been asked five times before? Each message takes just a few minutes to triage — but those minutes add up fast when you're the only one handling support.
Gemini API changes that. The Python system below reads incoming inquiries, asks Gemini to classify them and assign a priority score, then posts a structured notification to Slack. No specialized infrastructure required.
How the System Works
The flow is straightforward:
- A contact form submission comes in (via email, webhook, or any trigger)
- Python sends the content to Gemini API
- Gemini returns a structured JSON with category, priority, summary, and suggested response time
- The result gets posted to your Slack channel with color-coded formatting
This works with whatever form tool you're using — Google Forms, Typeform, a custom contact page — as long as you can extract the subject and body text.
Prerequisites
Install the required packages:
pip install google-genai requestsSet your environment variables:
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"Get your Gemini API key from Google AI Studio for free. For a refresher on getting started with the API, see Gemini API Quickstart. For Slack, create an Incoming Webhooks app in your workspace settings and copy the webhook URL for the channel you want notifications sent to.
Classifying Inquiries with Gemini API
Here's the core function that sends inquiry content to Gemini and gets back structured classification data:
import os
import json
from google import genai
from google.genai import types
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
def classify_inquiry(subject: str, body: str) -> dict:
"""
Classify an inquiry and return a priority score.
Returns:
{
"category": str,
"priority": int (1-5),
"priority_reason": str,
"summary": str,
"suggested_response_time": str
}
"""
prompt = f"""
Analyze the following support inquiry.
Subject: {subject}
Body: {body}
Return a JSON object with this exact structure:
{{
"category": "bug_report | feature_request | usage_question | billing | other",
"priority": integer from 1 to 5 (5 = highest urgency),
"priority_reason": "one sentence explaining the priority score",
"summary": "inquiry summary in 10 words or fewer",
"suggested_response_time": "immediate | within 24 hours | within 3 business days | within 1 week"
}}
Priority guidelines:
- 5: Service-wide outage, data loss risk, security issue
- 4: Specific feature broken, billing problem
- 3: Performance issue, partial feature malfunction
- 2: Feature request, improvement suggestion
- 1: General usage question
"""
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
response_mime_type="application/json",
temperature=0.1, # low temperature for consistent classification
),
)
return json.loads(response.text)A note on temperature=0.1: classification tasks benefit from consistency over creativity. You want the same type of message to always get the same category. In practice, temperature=0 can occasionally prepend stray characters before the JSON — 0.1 has been more reliable in my testing.
Setting response_mime_type="application/json" tells Gemini to return raw JSON rather than wrapping it in a markdown code block. Without this, you'd need to strip the ```json delimiters before parsing. For a deeper look at structured output patterns, see the Gemini Structured Output Guide.
Sending Priority-Aware Slack Notifications
This function formats the classification result into a color-coded Slack attachment and sends it:
import requests
from datetime import datetime
def send_slack_notification(inquiry_data: dict, classification: dict) -> bool:
"""
Post classification results to Slack with priority-aware formatting.
Returns:
True on success, raises on error
"""
category_emoji = {
"bug_report": "🐛",
"feature_request": "✨",
"usage_question": "❓",
"billing": "💳",
"other": "📝",
}
priority_colors = {
5: "#FF0000", # red — urgent
4: "#FF6600", # orange — high
3: "#FFCC00", # yellow — medium
2: "#00CC00", # green — low
1: "#999999", # gray — minimal
}
category = classification.get("category", "other")
priority = classification.get("priority", 1)
emoji = category_emoji.get(category, "📝")
color = priority_colors.get(priority, "#999999")
# Escalate header text for high-priority items
if priority >= 5:
header = "🚨 <!channel> URGENT: Immediate response needed"
elif priority >= 4:
header = f"⚠️ High-priority inquiry {emoji}"
else:
header = f"{emoji} New inquiry (priority {priority}/5)"
payload = {
"text": header,
"attachments": [
{
"color": color,
"fields": [
{
"title": "Summary",
"value": classification.get("summary", ""),
"short": False,
},
{
"title": "Category",
"value": category,
"short": True,
},
{
"title": "Priority",
"value": f"{'⭐' * priority} ({priority}/5)",
"short": True,
},
{
"title": "Suggested Response Time",
"value": classification.get("suggested_response_time", ""),
"short": True,
},
{
"title": "Reasoning",
"value": classification.get("priority_reason", ""),
"short": False,
},
{
"title": "From",
"value": inquiry_data.get("email", "unknown"),
"short": True,
},
],
"footer": f"Received: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}",
}
],
}
response = requests.post(
os.environ["SLACK_WEBHOOK_URL"],
json=payload,
timeout=10,
)
response.raise_for_status()
return TruePutting It All Together
The main handler ties classification and notification together, with proper error handling for production use:
def handle_inquiry(inquiry_data: dict) -> dict:
"""
Accept raw inquiry data, classify it, and send a Slack notification.
inquiry_data fields:
subject: email subject or form field title
body: full message content
email: sender's email address
name: sender's name (optional)
"""
classification = None
try:
# Step 1: classify with Gemini
classification = classify_inquiry(
subject=inquiry_data.get("subject", ""),
body=inquiry_data.get("body", ""),
)
# Step 2: notify Slack
send_slack_notification(inquiry_data, classification)
return {"status": "success", "classification": classification}
except json.JSONDecodeError as e:
# Gemini returned non-JSON — notify Slack with a fallback classification
print(f"JSON parse error: {e}")
fallback = {
"category": "other",
"priority": 3,
"priority_reason": "Auto-classification failed — manual review needed",
"summary": inquiry_data.get("subject", "")[:50],
"suggested_response_time": "within 24 hours",
}
send_slack_notification(inquiry_data, fallback)
return {"status": "fallback", "error": str(e)}
except requests.exceptions.RequestException as e:
# Classification succeeded but Slack failed
print(f"Slack notification failed: {e}")
return {
"status": "classified_but_notify_failed",
"classification": classification,
}
except Exception as e:
print(f"Unexpected error: {e}")
raise
# Quick test
if __name__ == "__main__":
test_inquiry = {
"subject": "Can't log in since this morning",
"body": "I was using the app fine yesterday but now I'm getting an authentication error every time I try to log in. I haven't changed my password.",
"email": "user@example.com",
"name": "Jane Smith",
}
result = handle_inquiry(test_inquiry)
print(result["classification"])
# Expected: category=bug_report, priority=4, suggested_response_time=within 24 hoursWhat I've Learned Running This in Production
A few things stood out after running this for a couple of months:
The prompt wording matters more than you'd expect. The specificity of your priority guidelines directly affects classification consistency. Vague criteria like "high priority = urgent" produce inconsistent results. Concrete examples — "priority 5 = service-wide outage or data loss risk" — give Gemini a clear reference point.
Emotionally charged messages get slightly elevated priority. A furious bug report tends to score slightly higher than a calm one describing the same issue. This is actually reasonable behavior: a visibly frustrated user probably needs a faster response regardless of technical severity.
The cost is negligible. With Gemini 2.5 Flash, each classification request costs well under $0.01. For most indie projects handling under a few hundred inquiries per month, this is effectively free. For error handling strategies that keep costs predictable, see Gemini API Error Handling Guide.
Connecting It to Google Forms
If you use Google Forms, the simplest approach is to set up a Flask or FastAPI webhook endpoint and call it from an Apps Script trigger on form submission:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhook/inquiry", methods=["POST"])
def inquiry_webhook():
data = request.get_json()
if not data:
return jsonify({"error": "Invalid JSON"}), 400
result = handle_inquiry(data)
return jsonify(result)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)Deploy this to Cloud Run and it only runs when a request comes in — no always-on server costs. Railway is another solid option if you prefer minimal configuration.
Next Steps
The biggest practical win from this system isn't speed — it's catching things that would have slipped through the cracks. Urgent bugs that arrive at 2am now surface in Slack instead of waiting until morning.
Start by running the test script with a few real inquiries from your existing backlog. Adjust the category definitions and priority thresholds to match your service's reality, then hook it up to your form. The full working code is above — copy, configure your environment variables, and you're ready to go.