Operations & Admin
hr people
How to Build an Employee Offboarding Automation System
Automate account deactivation, asset recovery, and exit workflows.
Jay Banlasan
The AI Systems Guy
When someone leaves your company, forgetting to revoke access is a security risk. I built a system to automate the employee offboarding process so that account deactivation, asset recovery, and knowledge transfer happen in the right order every time.
This system reverses the provisioning pipeline and tracks every step to completion.
What You Need Before Starting
- Python 3.8+
- API access to your identity and tool providers
- An asset tracking system (even a spreadsheet works)
- The access templates from your provisioning system
Step 1: Define the Offboarding Checklist
OFFBOARDING_STEPS = {
"immediate": [
{"task": "Disable email account", "owner": "IT", "security": True},
{"task": "Revoke VPN access", "owner": "IT", "security": True},
{"task": "Remove from Slack", "owner": "IT", "security": True},
{"task": "Revoke GitHub access", "owner": "IT", "security": True},
{"task": "Disable AWS credentials", "owner": "IT", "security": True}
],
"within_24h": [
{"task": "Collect company laptop", "owner": "IT", "security": False},
{"task": "Collect access badges", "owner": "Facilities", "security": False},
{"task": "Forward email to manager", "owner": "IT", "security": False},
{"task": "Transfer file ownership", "owner": "IT", "security": False}
],
"within_week": [
{"task": "Knowledge transfer documentation", "owner": "Manager", "security": False},
{"task": "Final payroll processing", "owner": "HR", "security": False},
{"task": "Benefits termination notice", "owner": "HR", "security": False},
{"task": "Exit interview scheduled", "owner": "HR", "security": False},
{"task": "Remove from payroll system", "owner": "HR", "security": False}
]
}
Step 2: Create the Offboarding Tracker
import sqlite3
from datetime import datetime, timedelta
def init_offboarding_db(db_path="offboarding.db"):
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS offboarding (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_name TEXT,
department TEXT,
last_day TEXT,
initiated_at TEXT,
status TEXT DEFAULT 'in_progress'
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS offboarding_tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
offboarding_id INTEGER,
task TEXT,
owner TEXT,
phase TEXT,
due_date TEXT,
security_critical INTEGER,
completed INTEGER DEFAULT 0,
completed_at TEXT
)
""")
conn.commit()
return conn
def start_offboarding(conn, employee_name, department, last_day):
cursor = conn.execute(
"INSERT INTO offboarding (employee_name, department, last_day, initiated_at) VALUES (?,?,?,?)",
(employee_name, department, last_day, datetime.now().isoformat())
)
offboarding_id = cursor.lastrowid
last_day_dt = datetime.strptime(last_day, "%Y-%m-%d")
for phase, tasks in OFFBOARDING_STEPS.items():
if phase == "immediate":
due = last_day
elif phase == "within_24h":
due = (last_day_dt + timedelta(days=1)).strftime("%Y-%m-%d")
else:
due = (last_day_dt + timedelta(days=7)).strftime("%Y-%m-%d")
for task in tasks:
conn.execute(
"INSERT INTO offboarding_tasks (offboarding_id, task, owner, phase, due_date, security_critical) VALUES (?,?,?,?,?,?)",
(offboarding_id, task["task"], task["owner"], phase, due, int(task["security"]))
)
conn.commit()
return offboarding_id
Step 3: Execute Security-Critical Steps First
def execute_security_steps(conn, offboarding_id):
tasks = conn.execute("""
SELECT id, task FROM offboarding_tasks
WHERE offboarding_id=? AND security_critical=1 AND completed=0
""", (offboarding_id,)).fetchall()
for task_id, task_name in tasks:
print(f"Executing: {task_name}")
# API calls to revoke access would go here
conn.execute(
"UPDATE offboarding_tasks SET completed=1, completed_at=? WHERE id=?",
(datetime.now().isoformat(), task_id)
)
conn.commit()
return len(tasks)
Step 4: Track Progress and Escalate
def offboarding_status(conn, offboarding_id):
total = conn.execute(
"SELECT COUNT(*) FROM offboarding_tasks WHERE offboarding_id=?",
(offboarding_id,)
).fetchone()[0]
completed = conn.execute(
"SELECT COUNT(*) FROM offboarding_tasks WHERE offboarding_id=? AND completed=1",
(offboarding_id,)
).fetchone()[0]
overdue = conn.execute(
"SELECT task, owner, due_date FROM offboarding_tasks WHERE offboarding_id=? AND completed=0 AND due_date < ?",
(offboarding_id, datetime.now().strftime("%Y-%m-%d"))
).fetchall()
return {
"progress": f"{completed}/{total}",
"percent": round(completed/total*100) if total else 0,
"overdue_tasks": [{"task": t[0], "owner": t[1], "due": t[2]} for t in overdue]
}
Step 5: Generate the Offboarding Report
def generate_report(conn, offboarding_id):
info = conn.execute(
"SELECT employee_name, department, last_day FROM offboarding WHERE id=?",
(offboarding_id,)
).fetchone()
status = offboarding_status(conn, offboarding_id)
report = f"# Offboarding Report: {info[0]}\n\n"
report += f"Department: {info[1]} | Last Day: {info[2]}\n"
report += f"Progress: {status['progress']} ({status['percent']}%)\n\n"
if status["overdue_tasks"]:
report += "## Overdue Tasks\n\n"
for task in status["overdue_tasks"]:
report += f"- {task['task']} (assigned to {task['owner']}, due {task['due']})\n"
return report
What to Build Next
Connect this to your provisioning system so offboarding automatically reverses every access grant from onboarding. Full lifecycle management with one data source.
Related Reading
- Building Your First Automation: A Complete Guide - automation step by step
- The Automation Decision Tree - prioritizing what to automate
- How to Audit Your Operations for AI Opportunities - finding high-value automation targets
Want this system built for your business?
Get a free assessment. We will map every system your business needs and show you the ROI.
Get Your Free Assessment