Skip to main content
RapidDev - Software Development Agency
outsystems-tutorial

Timers and Scheduled Jobs: Patterns and Best Practices

Timers in OutSystems are background scheduled jobs defined in the Processes tab. Create a Timer, assign it a Server Action to execute, and set a schedule using cron-like syntax. Trigger a timer on demand from Server Actions using the Wake<TimerName> system action. Monitor execution and errors in Service Center → Monitoring → Timers. In ODC, Timers live within the app (no BPT) and are configured the same way.

What you'll learn

  • How to create a Timer in the Processes tab and assign it a Server Action
  • Configuring timer schedules using OutSystems schedule syntax
  • Triggering a timer on demand using Wake<TimerName> from another Server Action
  • Adding robust error handling inside timer Server Actions to prevent silent failures
  • Monitoring timer execution and errors in Service Center (O11) or ODC Portal (ODC)
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate9 min read30-40 minOutSystems 11 and ODCMarch 2026RapidDev Engineering Team
TL;DR

Timers in OutSystems are background scheduled jobs defined in the Processes tab. Create a Timer, assign it a Server Action to execute, and set a schedule using cron-like syntax. Trigger a timer on demand from Server Actions using the Wake<TimerName> system action. Monitor execution and errors in Service Center → Monitoring → Timers. In ODC, Timers live within the app (no BPT) and are configured the same way.

Timers and Scheduled Jobs in OutSystems

Background processing is essential for any production app — nightly reports, email digests, data cleanup, batch imports. OutSystems Timers run server-side Server Actions on a schedule, completely separate from user interactions. They are reliable, built into the platform, and visible in Service Center without any infrastructure setup. This tutorial walks through creating, scheduling, monitoring, and triggering timers both on schedule and on demand.

Prerequisites

  • Service Studio connected to an O11 environment with Processes tab available
  • A Server Action ready to be scheduled (or you will create one in this tutorial)
  • Service Center access for monitoring (environment-url/ServiceCenter)

Step-by-step guide

1

Create a Timer in the Processes tab

In Service Studio, click the Processes tab (right panel — the stopwatch icon). You see 'Timers' in the tree. Right-click Timers → 'Add Timer'. Name it 'SendDailyDigestEmail'. In the Properties Panel for the new Timer: - Action: click the dropdown → select the Server Action to run (e.g., 'ProcessDailyDigest'). If the Server Action does not exist yet, create it first in Logic tab → Server Actions. - Schedule: the cron-like schedule (set in the next step) - Default Schedule Timeout: 20 minutes (platform default; increase for long-running jobs) If the Server Action does not yet exist: Logic tab → Server Actions → right-click → Add Server Action → name it 'ProcessDailyDigest'. You will fill in the logic in a later step.

Expected result: A Timer named 'SendDailyDigestEmail' appears in Processes tab → Timers, linked to the ProcessDailyDigest Server Action.

2

Configure the timer schedule

Select the Timer in the Processes tab. In the Properties Panel, find the Schedule property. Click the expression field → the Schedule editor opens. OutSystems schedule syntax: - '00 00 8 * * *' — every day at 8:00 AM - '00 00 * * * *' — every hour at minute 0 - '00 */30 * * * *' — every 30 minutes - '00 00 8 * * 1' — every Monday at 8:00 AM (1=Monday, 7=Sunday) - '00 00 8 1 * *' — first day of every month at 8:00 AM Schedule field format: 'SS MM HH DD MO WD' (Seconds Minutes Hours DayOfMonth Month WeekDay) For a daily digest at 7 AM: Schedule = '00 00 7 * * *' For processing that runs every 15 minutes: Schedule = '00 */15 * * * *' All schedule times are in the server's local timezone. In ODC, all times are UTC — factor this into your schedule.

typescript
1/* Timer schedule examples */
2"00 00 7 * * *" /* Every day at 07:00 */
3"00 00 8 * * 1" /* Every Monday at 08:00 */
4"00 */15 * * * *" /* Every 15 minutes */
5"00 00 2 1 * *" /* First of each month at 02:00 */
6"00 30 17 * * 5" /* Every Friday at 17:30 */

Expected result: The Timer schedule is set. After publishing, the next scheduled run appears in Service Center → Monitoring → Timers.

3

Write the Timer's Server Action with error handling

Open the ProcessDailyDigest Server Action. Timer Server Actions must be self-contained — they have no input parameters (timers start with no context) and typically no output parameters. Action flow for a batch email digest: Start → GetUsersForDigest Aggregate (filter: User.WantsDigest = True) → If: GetUsersForDigest.List.Empty → [True] End (nothing to process) → For Each User in GetUsersForDigest.List → SendDigestEmail Server Action (UserId = User.Id) → End Always add an AllExceptions handler to the Timer Server Action: Exception Handler (AllExceptions) → LogError('HRModule', 'ProcessDailyDigest', ExceptionMessage) → End (do NOT re-raise — let the timer complete without crashing) If you re-raise the exception inside a timer, OutSystems logs it as a timer failure in Service Center but the next scheduled run still fires. Catching and logging gives you more control over partial failure behavior.

typescript
1/* Server Action: ProcessDailyDigest */
2Start
3 --> Aggregate: GetUsersForDigest
4 Filter: User.WantsDigest = True and User.IsActive = True
5 --> If: GetUsersForDigest.List.Empty
6 [True] --> End
7 --> For Each: User in GetUsersForDigest.List
8 --> SendDigestEmail:
9 UserId = GetUsersForDigest.List.Current.User.Id
10 --> End
11
12Exception Handler (AllExceptions)
13 --> LogError:
14 ModuleName = "HRModule"
15 Source = "ProcessDailyDigest"
16 Message = "Timer failed: " + ExceptionMessage
17 --> End

Expected result: The ProcessDailyDigest action compiles with no TrueChange errors. The AllExceptions handler prevents a single failing email from crashing the entire batch.

4

Trigger a timer on demand with Wake<TimerName>

Sometimes you need to run a timer immediately rather than waiting for its scheduled time — for example, after a bulk import completes, trigger the processing timer right away. The Wake<TimerName> system action schedules the timer for immediate execution. It is available in Logic tab → System → Timer. To use: in any Server Action where you want to trigger the timer, drag WakeSendDailyDigestEmail (the system action named Wake + your Timer name) from the Toolbox or Logic tab → System into the action flow. Important behaviors: - WakeTimer does NOT run the timer synchronously. It queues it for the platform's timer execution engine. - If the timer is already running, WakeTimer queues a second run after the current one completes. - WakeTimer can be called from Server Actions only (not Client Actions directly — use a Client Action → Server Action → Wake pattern). Example: after bulk data import completes → WakeSendDailyDigestEmail → user sees 'Digest will be sent shortly'

typescript
1/* Server Action: TriggerDigestNow */
2Start
3 --> WakeSendDailyDigestEmail /* System action from Logic tab → System */
4 --> End
5
6/* Client Action that calls it */
7Start
8 --> TriggerDigestNow (Server Action)
9 --> Message: "Digest email has been queued" (Info)
10 --> End

Expected result: Calling TriggerDigestNow queues the timer. Within seconds, Service Center → Monitoring → Timers shows the timer in 'Running' state.

5

Monitor timer execution in Service Center

After publishing, navigate to Service Center → Monitoring → Timers. You see: - Timer Name: the timer you created - Next Run: the next scheduled execution time - Last Run: timestamp of the last execution - Status: Success, Error, or Running - Duration: how long the last run took in seconds - Error Message: populated if the last run failed For detailed logs, click the Timer name → History tab. Each execution row is expandable to show the full error stack trace if it failed. For errors in detail: Service Center → Monitoring → Error Logs — timer LogError entries appear here with full context. Performance tuning: if Duration is close to or exceeds the Default Schedule Timeout (20 min default), increase the timeout in the Timer's Properties Panel or optimize the action. Timers that exceed the timeout are killed by the platform.

Expected result: You can see the timer's execution history, duration, and any errors in Service Center without needing to read application logs manually.

Complete working example

Timer_BatchEmailDigest.txt
1/* ============================================================
2 TIMER: SendDailyDigestEmail
3 Processes tab Timers SendDailyDigestEmail
4 Schedule: '00 00 7 * * *' (every day at 07:00)
5 Timeout: 30 minutes
6 Action: ProcessDailyDigest
7 ============================================================ */
8
9/* SERVER ACTION: ProcessDailyDigest
10 No input/output parameters
11*/
12Start
13 --> Aggregate: GetActiveUsersWithDigest
14 Source: User
15 Filter: User.WantsDigest = True
16 and User.IsActive = True
17 and User.LastDigestSent < AddDays(CurrDate(), -1)
18 Max Records: Site.DigestBatchSize /* Site Property */
19 --> If: GetActiveUsersWithDigest.List.Empty
20 [True] --> End /* Nothing to process today */
21 --> For Each: UserRecord in GetActiveUsersWithDigest.List
22 --> SendDigestEmail:
23 UserId = UserRecord.User.Id
24 Email = UserRecord.User.Email
25 Name = UserRecord.User.Name
26 --> UpdateUserLastDigestSent:
27 UserId = UserRecord.User.Id
28 SentAt = CurrDateTime()
29 --> End
30
31Exception Handler (AllExceptions)
32 --> LogError:
33 ModuleName = "HRModule"
34 Source = "ProcessDailyDigest"
35 Message = "Digest timer error: " + ExceptionMessage
36 AdditionalData = "Batch size: " + IntegerToText(Site.DigestBatchSize)
37 --> End /* Do NOT re-raise - allow next batch to run */
38
39/* SERVER ACTION: TriggerDigestNow (on-demand Wake)
40 Called when admin wants immediate run
41*/
42Start
43 --> WakeSendDailyDigestEmail /* System action */
44 --> End

Common mistakes

Why it's a problem: Timer Server Action has no error handling and one failing record stops the entire batch

How to avoid: Move the exception handler inside the For Each loop, not outside it. Catching errors per-record allows the batch to continue even if individual records fail. Log the failing record's Id so you can investigate later.

Why it's a problem: Calling WakeTimer from a Client Action directly

How to avoid: WakeTimer is a system Server Action. Create a Server Action that calls WakeTimer, then call that Server Action from your Client Action.

Why it's a problem: Not filtering the aggregate in the timer — processing all records every run

How to avoid: Always filter timer aggregates by a 'needs processing' condition (e.g., LastProcessedAt < CurrDate(), Status = Entities.Status.Pending). Processing all records every run causes exponential growth in execution time as data grows.

Why it's a problem: Setting the timer schedule in local time without considering server timezone

How to avoid: Service Center shows the server's local timezone. If your server is in UTC and your users are in EST, a 9 AM EST schedule needs to be set as '00 00 14 * * *' (14:00 UTC). In ODC, all timers run in UTC — adjust accordingly.

Best practices

  • Always add an AllExceptions handler to every timer Server Action — an unhandled exception marks the timer as failed in Service Center but does not prevent future scheduled runs.
  • Use a Site Property for batch size (e.g., Site.DigestBatchSize = 100) so you can tune it in Service Center without redeploying.
  • Track the last-processed timestamp on records (LastDigestSent, ProcessedAt) to implement incremental processing and avoid reprocessing records on every run.
  • Set the Timer's Default Schedule Timeout to 150% of the expected maximum runtime — this gives buffer without leaving zombie processes running indefinitely.
  • Test timers using WakeTimer from a temporary test button during development so you do not have to wait for the scheduled time.
  • In O11 clustered environments, timers run on only one server at a time (OutSystems manages this). Do not add distributed locking unless you are running custom batch logic outside timers.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a daily email digest timer in OutSystems 11. Explain how to: (1) create a Timer in the Processes tab and link it to a Server Action, (2) set the schedule to run every day at 7 AM using OutSystems schedule syntax, (3) write the Server Action with a For Each loop over an aggregate and an AllExceptions error handler, (4) trigger the timer on demand using the WakeTimer system action. Use OutSystems action flow arrow notation.

OutSystems Prompt

Create an OutSystems timer for daily email digest processing. In Processes tab, add Timer 'SendDailyDigestEmail' with schedule '00 00 7 * * *' linked to Server Action ProcessDailyDigest. The Server Action should: fetch users with WantsDigest=True and IsActive=True using an aggregate, loop through them with For Each, call SendDigestEmail for each, and have an AllExceptions handler that calls LogError. Also create a TriggerDigestNow Server Action that calls WakeSendDailyDigestEmail.

Frequently asked questions

Can I run a timer manually from Service Center without code?

Yes. In Service Center → Factory → Modules → [Module] → Timers tab, click the 'Run Now' link next to any timer. This immediately queues the timer for execution, equivalent to calling WakeTimer in code. Useful for operations teams testing without developer involvement.

What happens if a timer is still running when its next scheduled time arrives?

OutSystems checks if the timer is already running before starting the next scheduled execution. If it is running, the next run is skipped to prevent concurrent executions of the same timer. The timer resumes its normal schedule on the following cycle.

How is an OutSystems O11 Timer different from ODC's Timer implementation?

In O11, Timers use the Business Process Technology (BPT) engine and appear under the Processes tab. In ODC, Timers are a first-class concept within the app (no BPT dependency) also in the Processes tab of ODC Studio. O11 Timers run on the application server pool; ODC Timers run as container tasks with automatic scaling. The schedule syntax and WakeTimer pattern are identical in both.

Can I pass parameters to a timer's action?

No — Timers always call the linked Server Action with no input parameters. This is by design: timers are platform-triggered, not user-triggered. If you need to run processing for a specific record, use WakeTimer to trigger the timer and have the timer read a queue entity (records with Status = Pending) to determine what to process next.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.