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
Create a Timer in the Processes tab
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.
Configure the timer schedule
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.
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.
Write the Timer's Server Action with error handling
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.
1/* Server Action: ProcessDailyDigest */2Start3 --> Aggregate: GetUsersForDigest4 Filter: User.WantsDigest = True and User.IsActive = True5 --> If: GetUsersForDigest.List.Empty6 [True] --> End7 --> For Each: User in GetUsersForDigest.List8 --> SendDigestEmail:9 UserId = GetUsersForDigest.List.Current.User.Id10 --> End1112Exception Handler (AllExceptions)13 --> LogError:14 ModuleName = "HRModule"15 Source = "ProcessDailyDigest"16 Message = "Timer failed: " + ExceptionMessage17 --> EndExpected result: The ProcessDailyDigest action compiles with no TrueChange errors. The AllExceptions handler prevents a single failing email from crashing the entire batch.
Trigger a timer on demand with Wake<TimerName>
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'
1/* Server Action: TriggerDigestNow */2Start3 --> WakeSendDailyDigestEmail /* System action from Logic tab → System */4 --> End56/* Client Action that calls it */7Start8 --> TriggerDigestNow (Server Action)9 --> Message: "Digest email has been queued" (Info)10 --> EndExpected result: Calling TriggerDigestNow queues the timer. Within seconds, Service Center → Monitoring → Timers shows the timer in 'Running' state.
Monitor timer execution in Service Center
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
1/* ============================================================2 TIMER: SendDailyDigestEmail3 Processes tab → Timers → SendDailyDigestEmail4 Schedule: '00 00 7 * * *' (every day at 07:00)5 Timeout: 30 minutes6 Action: ProcessDailyDigest7 ============================================================ */89/* SERVER ACTION: ProcessDailyDigest10 No input/output parameters11*/12Start13 --> Aggregate: GetActiveUsersWithDigest14 Source: User15 Filter: User.WantsDigest = True16 and User.IsActive = True17 and User.LastDigestSent < AddDays(CurrDate(), -1)18 Max Records: Site.DigestBatchSize /* Site Property */19 --> If: GetActiveUsersWithDigest.List.Empty20 [True] --> End /* Nothing to process today */21 --> For Each: UserRecord in GetActiveUsersWithDigest.List22 --> SendDigestEmail:23 UserId = UserRecord.User.Id24 Email = UserRecord.User.Email25 Name = UserRecord.User.Name26 --> UpdateUserLastDigestSent:27 UserId = UserRecord.User.Id28 SentAt = CurrDateTime()29 --> End3031Exception Handler (AllExceptions)32 --> LogError:33 ModuleName = "HRModule"34 Source = "ProcessDailyDigest"35 Message = "Digest timer error: " + ExceptionMessage36 AdditionalData = "Batch size: " + IntegerToText(Site.DigestBatchSize)37 --> End /* Do NOT re-raise - allow next batch to run */3839/* SERVER ACTION: TriggerDigestNow (on-demand Wake)40 Called when admin wants immediate run41*/42Start43 --> WakeSendDailyDigestEmail /* System action */44 --> EndCommon 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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation