TaskTx is the server-side SDK for building Scheduled Tasks - automated workflows that run on predefined schedules. Tasks are perfect for:
Tasks run server-side on a schedule. Your task code receives a tx capability object:
// Example: Monthly spending report
const thisMonth = new Date();
const firstDay = new Date(thisMonth.getFullYear(), thisMonth.getMonth(), 1)
.toISOString().split('T')[0];
const lastDay = new Date(thisMonth.getFullYear(), thisMonth.getMonth() + 1, 0)
.toISOString().split('T')[0];
// Search invoices from this month
const invoices = await tx.searchEntities({
query: "",
filters: {
docType: "INVOICE",
date: { gte: firstDay, lte: lastDay }
},
limit: 1000
});
// Calculate total
let total = 0;
for (const invoice of invoices.results) {
total += invoice.preview.amount || 0;
}
// Send email report
await tx.sendEmail({
title: `Monthly Spending Report - ${thisMonth.toLocaleString('default', { month: 'long', year: 'numeric' })}`,
message: `Total spending this month: $${total.toLocaleString(undefined, { minimumFractionDigits: 2 })}`
});
// Create in-app notification
tx.addNotification({
level: "info",
message: `Monthly report sent. Total: $${total.toLocaleString()}`
});
// Search entities (same as uiTx)
const results = await tx.searchEntities({
query: "vendor name",
filters: {
docType: "INVOICE",
date: { gte: "2025-01-01", lte: "2025-12-31" },
amount: { gte: 100 },
currency: "USD"
},
limit: 100
});
// Get full entity data
const entity = await tx.getEntity({ entityId: "doc123_items_0" });
// List documents
const documents = await tx.listDocuments({
docType: "INVOICE",
folderId: "folder123",
date: { gte: "2025-01-01" }
});
// Aggregate data
const aggregated = await tx.aggregate({
from: "entities",
metrics: [
{ op: "sum", field: "amount", as: "total" },
{ op: "count", as: "count" }
],
groupBy: ["month", "docType"],
where: [
{ field: "docType", op: "=", value: "INVOICE" }
]
});
// Create reconciliation link
const result = await tx.createReconciliationLink({
sourceRef: { entityId: "invoice123", ... },
targetRef: { entityId: "payment456", ... },
amount: 1000.00,
currency: "USD",
edgeType: "paid_by"
});
// List reconciliation links
const links = await tx.listReconciliationLinks({
entityId: "invoice123"
});
// Trace money flow
const trace = await tx.traceReconciliation({
startEntityId: "invoice123",
direction: "both",
maxDepth: 10
});
// Send email to task's registered notification emails
await tx.sendEmail({
title: "Report Title",
message: "Report content here"
});
// Note: 'to' parameter is NOT required - uses task.notificationEmails automatically
// Create in-app notification (org-scoped)
tx.addNotification({
level: "info", // "info" | "warning" | "error"
message: "Task completed successfully"
});
// Log messages for debugging
tx.log("info", "Processing started");
tx.log("warn", "Rate limit approaching");
tx.log("error", "API request failed", { status: 500 });
// Check if value is a non-empty string
if (tx.utils.isNonEmptyString(value)) {
// value is guaranteed to be a string
}
// Parse number from string or number
const num = tx.utils.parseNumber("123.45");
// Check if value is nullish
if (tx.utils.isNullish(value)) {
// value is null or undefined
}
// Format currency
const formatted = tx.utils.formatCurrency(1234.56, "USD");
// Calculate monthly spending by vendor
const thisMonth = new Date();
const firstDay = new Date(thisMonth.getFullYear(), thisMonth.getMonth(), 1)
.toISOString().split('T')[0];
const lastDay = new Date(thisMonth.getFullYear(), thisMonth.getMonth() + 1, 0)
.toISOString().split('T')[0];
// Aggregate by vendor
const data = await tx.aggregate({
from: "entities",
metrics: [{ op: "sum", field: "amount", as: "total" }],
groupBy: ["vendorName"],
where: [
{ field: "docType", op: "=", value: "INVOICE" },
{ field: "date", op: ">=", value: firstDay },
{ field: "date", op: "<=", value: lastDay }
]
});
// Build report
let report = `Monthly Spending Report - ${thisMonth.toLocaleString('default', { month: 'long', year: 'numeric' })}\n\n`;
let grandTotal = 0;
for (const row of data.rows) {
report += `${row.vendorName}: $${row.total.toLocaleString(undefined, { minimumFractionDigits: 2 })}\n`;
grandTotal += row.total || 0;
}
report += `\nGrand Total: $${grandTotal.toLocaleString(undefined, { minimumFractionDigits: 2 })}`;
// Send email
await tx.sendEmail({
title: `Monthly Spending Report - ${thisMonth.toLocaleString('default', { month: 'long', year: 'numeric' })}`,
message: report
});
tx.addNotification({
level: "info",
message: `Monthly report sent. Total: $${grandTotal.toLocaleString()}`
});
// Check if invoice from VendorX was received this month
const thisMonth = new Date();
const firstDay = new Date(thisMonth.getFullYear(), thisMonth.getMonth(), 1)
.toISOString().split('T')[0];
const lastDay = new Date(thisMonth.getFullYear(), thisMonth.getMonth() + 1, 0)
.toISOString().split('T')[0];
const invoices = await tx.searchEntities({
query: "VendorX",
filters: {
docType: "INVOICE",
date: { gte: firstDay, lte: lastDay }
},
limit: 10
});
if (invoices.total === 0) {
// No invoice found - send alert
await tx.sendEmail({
title: "⚠️ Missing Invoice Alert",
message: `No invoice from VendorX found for ${thisMonth.toLocaleString('default', { month: 'long', year: 'numeric' })}.`
});
tx.addNotification({
level: "warning",
message: "Missing invoice from VendorX this month"
});
} else {
tx.log("info", `Found ${invoices.total} invoice(s) from VendorX`);
}
// Match invoices with payments from last 30 days
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const dateFrom = thirtyDaysAgo.toISOString().split('T')[0];
const dateTo = new Date().toISOString().split('T')[0];
// Search invoices
const invoices = await tx.searchEntities({
query: "vendor X",
filters: {
docType: "INVOICE",
date: { gte: dateFrom, lte: dateTo }
},
limit: 100
});
// Search payments
const payments = await tx.searchEntities({
query: "vendor Y",
filters: {
docType: "PAYMENT",
date: { gte: dateFrom, lte: dateTo }
},
limit: 100
});
// Match by amount and currency
let matchesCreated = 0;
const matchDetails = [];
for (const invoice of invoices.results) {
const invoiceEntity = await tx.getEntity({ entityId: invoice.ref.entityId });
const invoiceAmount = invoiceEntity.data.amount;
const invoiceCurrency = invoiceEntity.data.currency;
for (const payment of payments.results) {
const paymentEntity = await tx.getEntity({ entityId: payment.ref.entityId });
const paymentAmount = paymentEntity.data.amount;
const paymentCurrency = paymentEntity.data.currency;
// Match if amounts are equal (within 0.01 tolerance) and currencies match
if (paymentCurrency === invoiceCurrency &&
Math.abs(paymentAmount - invoiceAmount) < 0.01) {
const result = await tx.createReconciliationLink({
sourceRef: invoice.ref,
targetRef: payment.ref,
amount: invoiceAmount,
currency: invoiceCurrency,
edgeType: "paid_by"
});
if (result.ok) {
matchesCreated++;
matchDetails.push({
invoice: invoice.preview.description,
payment: payment.preview.description,
amount: invoiceAmount,
currency: invoiceCurrency
});
}
}
}
}
// Send report
const report = `Reconciliation Report (Last 30 Days)\n\n` +
`Matches Created: ${matchesCreated}\n\n` +
matchDetails.map(m =>
`- ${m.invoice} ↔ ${m.payment} (${m.currency} ${m.amount})`
).join("\n");
await tx.sendEmail({
title: "Reconciliation Report",
message: report
});
tx.addNotification({
level: "info",
message: `Created ${matchesCreated} reconciliation link(s)`
});
Tasks run with the following context:
sendEmail)Tasks are org-wide - they don't have document context like formula fields do.
aggregate() instead of iteratingtx.log() for debuggingaddNotification() for in-app visibilityTasks run automatically on their schedule. Email failures are logged but don't fail the task execution.
On This Page
OverviewQuick StartCore APISearch OperationsEntity OperationsAggregationReconciliationTask-Specific CapabilitiesEmail SendingApp NotificationsLoggingUtilitiesComplete ExamplesExample 1: Monthly Spending ReportExample 2: Verify Invoice ReceivedExample 3: Automated ReconciliationSecurity & LimitsTask ContextBest Practices