Reminding inactive members
This example shows how to use DocSpace audit events to identify room members who are not participating. It loads audit events for the last N days, counts events per user for selected rooms, and sends a reminder to members whose activity is below a defined threshold. Optionally, it sends a short summary report to a room owner/manager.
Before you start
- Replace
https://yourportal.onlyoffice.comandYOUR_API_KEYwith your actual DocSpace portal URL and API key. Ensure you have the necessary data and permissions to perform these operations. - Before you can make requests to the API, you need to authenticate. Check out the Personal access tokens page to learn how to obtain and use access tokens.
Full example
- Node.js
- Python
// Set API base URL
const API_HOST = process.env.DOCSPACE_API_HOST; // Set DOCSPACE_API_HOST in env (recommended). For quick tests you can temporarily paste your portal URL here.
const API_KEY = process.env.DOCSPACE_API_KEY; // Set DOCSPACE_API_KEY in env (recommended). For quick tests you can temporarily paste token here.
const MONITORED_ROOM_IDS = [463996, 274818];
const PERIOD_DAYS = 30;
const MIN_ACTIVITY = 1;
const MANAGER_EMAIL = 'manager@company.com';
const HEADERS = {
Accept: 'application/json',
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
};
async function docspaceRequest(path: string, method = 'GET', body: any = null) {
const url = `${API_HOST}${path}`;
try {
const res = await fetch(url, {
method: method.toUpperCase(),
headers: HEADERS,
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
const t = await res.text();
console.log(`DocSpace request failed. Status: ${res.status}, Message: ${t}`);
return null;
}
return res.json();
} catch (err: any) {
console.log(`DocSpace request error: ${err?.message || err}`);
return null;
}
}
// Step 1: Get room members (placeholder)
function fetchRoomMembers(roomId: number) {
if (roomId === 463996) {
return [
{ id: 'user-aaa', displayName: 'Alice Smith', email: 'alice@example.com' },
{ id: 'user-bbb', displayName: 'Bob Johnson', email: 'bob@example.com' },
];
}
if (roomId === 274818) {
return [
{ id: 'user-ccc', displayName: 'Carol Davis', email: 'carol@example.com' },
];
}
return [];
}
// Step 2: Load audit events for the last PERIOD_DAYS
async function getAuditEvents(fromIso: string, toIso: string) {
const query = `from=${encodeURIComponent(fromIso)}&to=${encodeURIComponent(toIso)}`;
const data = await docspaceRequest(`/api/2.0/security/audit/events/filter?${query}`, 'GET');
if (!data || typeof data !== 'object') return [];
const events = (data as any).response;
return Array.isArray(events) ? events : [];
}
function getRoomIdFromEvent(evt: any) {
const direct =
evt.roomId ||
evt.spaceId ||
evt.folderId;
if (direct != null) return Number(direct);
const entity = evt.entity && typeof evt.entity === 'object' ? evt.entity : null;
if (!entity) return null;
const nested = entity.roomId || entity.spaceId;
if (nested == null) return null;
const n = Number(nested);
return Number.isFinite(n) ? n : null;
}
function isRoomActivityEvent(evt: any, roomIds: number[]) {
const entityType = String(evt.entityType || evt.targetType || evt.entity || '').toLowerCase();
const isFileOrFolder =
entityType.includes('file') ||
entityType.includes('document') ||
entityType.includes('folder') ||
entityType.includes('room');
if (!isFileOrFolder) return false;
const roomId = getRoomIdFromEvent(evt);
if (roomId == null) return false;
return roomIds.includes(roomId);
}
// Step 3: Build room -> user -> activityCount map
function buildRoomUserActivityMap(events: any[], roomIds: number[]) {
const result: Record<number, Record<string, number>> = {};
for (const evt of events) {
if (!isRoomActivityEvent(evt, roomIds)) continue;
const roomId = getRoomIdFromEvent(evt);
if (roomId == null) continue;
const userId = String(evt.userId || evt.account || '');
if (!userId) continue;
if (!result[roomId]) result[roomId] = {};
if (!result[roomId][userId]) result[roomId][userId] = 0;
result[roomId][userId] += 1;
}
return result;
}
// Step 4: Send reminders and manager report (placeholders)
function sendReminderToUser(member: any, roomId: number, periodDays: number) {
const to = member.email || member.displayName || 'user';
console.log('--- USER REMINDER ---');
console.log(`To: ${to}`);
console.log('Subject: Infrequent activity in DocSpace room');
console.log(
`Body:\nHello ${member.displayName || 'there'},\n` +
`We noticed that you had no activity in room ID ${roomId} during the last ${periodDays} days.\n` +
'If this room is still relevant to you, please review its contents.'
);
}
function sendManagerReport(lines: string[]) {
console.log('--- MANAGER REPORT ---');
console.log(`To: ${MANAGER_EMAIL}`);
console.log('Subject: Monthly report: infrequent room users');
if (!lines.length) {
console.log('Body:\nAll monitored room members were active during this period.');
return;
}
console.log('Body:');
for (const line of lines) console.log(line);
}
// Main workflow
(async () => {
try {
const now = new Date();
const from = new Date(now.getTime() - PERIOD_DAYS * 24 * 60 * 60 * 1000);
const fromIso = from.toISOString();
const toIso = now.toISOString();
console.log(`Checking infrequent users from ${fromIso} to ${toIso}...`);
const roomMembersMap: Record<number, any[]> = {};
for (const roomId of MONITORED_ROOM_IDS) {
roomMembersMap[roomId] = fetchRoomMembers(roomId);
}
const events = await getAuditEvents(fromIso, toIso);
const activityMap = buildRoomUserActivityMap(events, MONITORED_ROOM_IDS);
const managerReportLines: string[] = [];
for (const roomId of MONITORED_ROOM_IDS) {
const members = roomMembersMap[roomId] || [];
const roomActivity = activityMap[roomId] || {};
if (!members.length) continue;
console.log(`Room ${roomId}: checking ${members.length} member(s)...`);
for (const member of members) {
const userId = String(member.id || '');
const count = roomActivity[userId] || 0;
if (count < MIN_ACTIVITY) {
sendReminderToUser(member, roomId, PERIOD_DAYS);
managerReportLines.push(
`Room ${roomId}: user ${member.displayName} (${member.email || userId}) had 0 events in the last ${PERIOD_DAYS} days.`
);
}
}
}
sendManagerReport(managerReportLines);
console.log('Monthly infrequent-users check completed.');
} catch (err: any) {
console.error(err?.message || err);
}
})();
import os
import requests
from datetime import datetime, timedelta, timezone
from urllib.parse import urlencode
API_HOST = os.environ.get("DOCSPACE_API_HOST") # Set DOCSPACE_API_HOST in env (recommended). For quick tests you can temporarily paste your portal URL here.
API_KEY = os.environ.get("DOCSPACE_API_KEY") # Set DOCSPACE_API_KEY in env (recommended). For quick tests you can temporarily paste token here.
MONITORED_ROOM_IDS = [463996, 274818]
PERIOD_DAYS = 30
MIN_ACTIVITY = 1
MANAGER_EMAIL = "manager@company.com"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
}
def docspace_request(path, method="GET", json_body=None):
url = f"{API_HOST}{path}"
try:
response = requests.request(
method.upper(),
url,
headers=HEADERS,
json=json_body,
timeout=30,
)
if not (200 <= response.status_code < 300):
print(f"DocSpace request failed. Status: {response.status_code}, Message: {response.text}")
return None
return response.json()
except Exception as e:
print(f"DocSpace request error: {e}")
return None
# Step 1: Get room members (placeholder)
def fetch_room_members(room_id):
if room_id == 463996:
return [
{"id": "user-aaa", "displayName": "Alice Smith", "email": "alice@example.com"},
{"id": "user-bbb", "displayName": "Bob Johnson", "email": "bob@example.com"},
]
if room_id == 274818:
return [
{"id": "user-ccc", "displayName": "Carol Davis", "email": "carol@example.com"},
]
return []
# Step 2: Load audit events for the last PERIOD_DAYS
def get_audit_events(dt_from, dt_to):
query = urlencode({"from": dt_from.isoformat(), "to": dt_to.isoformat()})
data = docspace_request(f"/api/2.0/security/audit/events/filter?{query}", method="GET")
if not isinstance(data, dict) or "response" not in data:
return []
events = data.get("response")
return events if isinstance(events, list) else []
def get_room_id_from_event(evt):
room_id = evt.get("roomId") or evt.get("spaceId") or evt.get("folderId")
if room_id is not None:
try:
return int(room_id)
except Exception:
return None
entity = evt.get("entity")
if not isinstance(entity, dict):
return None
nested = entity.get("roomId") or entity.get("spaceId")
if nested is None:
return None
try:
return int(nested)
except Exception:
return None
def is_room_activity_event(evt, room_ids):
entity_type = str(evt.get("entityType") or evt.get("targetType") or evt.get("entity") or "").lower()
is_file_or_folder = (
"file" in entity_type or
"document" in entity_type or
"folder" in entity_type or
"room" in entity_type
)
if not is_file_or_folder:
return False
room_id = get_room_id_from_event(evt)
if room_id is None:
return False
return room_id in room_ids
# Step 3: Build room -> user -> activityCount map
def build_room_user_activity_map(events, room_ids):
result = {}
for evt in events:
if not is_room_activity_event(evt, room_ids):
continue
room_id = get_room_id_from_event(evt)
if room_id is None:
continue
user_id = str(evt.get("userId") or evt.get("account") or "")
if not user_id:
continue
if room_id not in result:
result[room_id] = {}
current = result[room_id].get(user_id, 0)
result[room_id][user_id] = current + 1
return result
# Step 4: Send reminders and manager report (placeholders)
def send_reminder_to_user(member, room_id, period_days):
to = member.get("email") or member.get("displayName") or "user"
print("--- USER REMINDER ---")
print(f"To: {to}")
print("Subject: Infrequent activity in DocSpace room")
print(
"Body:\n"
f"Hello {member.get('displayName') or 'there'},\n"
f"We noticed that you had no activity in room ID {room_id} during the last {period_days} days.\n"
"If this room is still relevant to you, please review its contents."
)
def send_manager_report(report_lines):
print("--- MANAGER REPORT ---")
print(f"To: {MANAGER_EMAIL}")
print("Subject: Monthly report: infrequent room users")
if not report_lines:
print("Body:\nAll monitored room members were active during this period.")
return
print("Body:")
for line in report_lines:
print(line)
# Main workflow
def monthly_infrequent_users_check():
now = datetime.now(timezone.utc)
dt_from = now - timedelta(days=PERIOD_DAYS)
print(f"Checking infrequent users from {dt_from.isoformat()} to {now.isoformat()}...")
room_members_map = {}
for room_id in MONITORED_ROOM_IDS:
room_members_map[room_id] = fetch_room_members(room_id)
events = get_audit_events(dt_from, now)
activity_map = build_room_user_activity_map(events, MONITORED_ROOM_IDS)
manager_report_lines = []
for room_id in MONITORED_ROOM_IDS:
members = room_members_map.get(room_id, [])
room_activity = activity_map.get(room_id, {})
if not members:
continue
print(f"Room {room_id}: checking {len(members)} member(s)...")
for member in members:
user_id = str(member.get("id") or "")
count = room_activity.get(user_id, 0)
if count < MIN_ACTIVITY:
send_reminder_to_user(member, room_id, PERIOD_DAYS)
manager_report_lines.append(
f"Room {room_id}: user {member.get('displayName')} "
f"({member.get('email') or user_id}) had 0 events in the last {PERIOD_DAYS} days."
)
send_manager_report(manager_report_lines)
print("Monthly infrequent-users check completed.")
if __name__ == "__main__":
monthly_infrequent_users_check()
Step 1: Get room members
The script loads room members for each monitored room.
In this example, fetch_room_members() is a placeholder returning static data. In a real integration, replace it with a real DocSpace endpoint for room members or sharing entries.
- Node.js
- Python
function fetchRoomMembers(roomId: number) {
if (roomId === 463996) {
return [
{ id: 'user-aaa', displayName: 'Alice Smith', email: 'alice@example.com' },
{ id: 'user-bbb', displayName: 'Bob Johnson', email: 'bob@example.com' },
];
}
if (roomId === 274818) {
return [
{ id: 'user-ccc', displayName: 'Carol Davis', email: 'carol@example.com' },
];
}
return [];
}
def fetch_room_members(room_id):
if room_id == 463996:
return [
{"id": "user-aaa", "displayName": "Alice Smith", "email": "alice@example.com"},
{"id": "user-bbb", "displayName": "Bob Johnson", "email": "bob@example.com"},
]
if room_id == 274818:
return [
{"id": "user-ccc", "displayName": "Carol Davis", "email": "carol@example.com"},
]
return []
Step 2: Retrieve audit events for the period
A GET request is sent to /api/2.0/security/audit/events/filter with:
from: start datetimeto: end datetime The response returns a list of audit events (usually under response).
- Node.js
- Python
async function getAuditEvents(fromIso: string, toIso: string) {
const query = `from=${encodeURIComponent(fromIso)}&to=${encodeURIComponent(toIso)}`;
const data = await docspaceRequest(`/api/2.0/security/audit/events/filter?${query}`, 'GET');
if (!data || typeof data !== 'object') return [];
const events = (data as any).response;
return Array.isArray(events) ? events : [];
}
from urllib.parse import urlencode
def get_audit_events(dt_from, dt_to):
query = urlencode({"from": dt_from.isoformat(), "to": dt_to.isoformat()})
data = docspace_request(f"/api/2.0/security/audit/events/filter?{query}", method="GET")
if not isinstance(data, dict) or "response" not in data:
return []
events = data.get("response")
return events if isinstance(events, list) else []
Step 3: Count activity per user in monitored rooms
The script filters audit events that belong to the monitored rooms and counts events per user.
If a user has fewer than MIN_ACTIVITY events, they are treated as inactive for the period.
- Node.js
- Python
function buildRoomUserActivityMap(events: any[], roomIds: number[]) {
const result: Record<number, Record<string, number>> = {};
for (const evt of events) {
if (!isRoomActivityEvent(evt, roomIds)) continue;
const roomId = getRoomIdFromEvent(evt);
if (roomId == null) continue;
const userId = String(evt.userId || evt.account || '');
if (!userId) continue;
if (!result[roomId]) result[roomId] = {};
if (!result[roomId][userId]) result[roomId][userId] = 0;
result[roomId][userId] += 1;
}
return result;
}
def build_room_user_activity_map(events, room_ids):
result = {}
for evt in events:
if not is_room_activity_event(evt, room_ids):
continue
room_id = get_room_id_from_event(evt)
if room_id is None:
continue
user_id = str(evt.get("userId") or evt.get("account") or "")
if not user_id:
continue
if room_id not in result:
result[room_id] = {}
current = result[room_id].get(user_id, 0)
result[room_id][user_id] = current + 1
return result
Step 4: Send reminders and a manager report
The script sends:
- a reminder to each inactive user (placeholder)
- one summary report to the manager (placeholder)
- Node.js
- Python
function sendReminderToUser(member: any, roomId: number, periodDays: number) {
const to = member.email || member.displayName || 'user';
console.log('--- USER REMINDER ---');
console.log(`To: ${to}`);
console.log('Subject: Infrequent activity in DocSpace room');
console.log(
`Body:\nHello ${member.displayName || 'there'},\n` +
`We noticed that you had no activity in room ID ${roomId} during the last ${periodDays} days.\n` +
'If this room is still relevant to you, please review its contents.'
);
}
def send_reminder_to_user(member, room_id, period_days):
to = member.get("email") or member.get("displayName") or "user"
print("--- USER REMINDER ---")
print(f"To: {to}")
print("Subject: Infrequent activity in DocSpace room")
print(
"Body:\n"
f"Hello {member.get('displayName') or 'there'},\n"
f"We noticed that you had no activity in room ID {room_id} during the last {period_days} days.\n"
"If this room is still relevant to you, please review its contents."
)