Jobs and email
Two things an app can do when nobody has it open: run on a schedule, and email
the people who use it. Both are declared in spryloom.yaml and shown on the
app's page before anyone signs in.
Scheduled jobs
app:
name: Office requests
slug: office-requests
description: Tracks office requests and reminds approvers every weekday.
runtime:
frontend: none
backend: node
start: node server.js
access:
visibility: invited
data:
postgres: true
tables: [requests]
uploads: false
jobs:
- name: morning-reminder
command: node jobs/remind.js
schedule: "0 9 * * 1-5"
timezone: America/New_York
A job is a script in your app. It runs with the app's database and secrets, on
the same network with the same limits, so it can read and write what the app
can and reach nothing the app cannot. It is stopped when the command exits, or
at timeout_minutes.
What the beta allows:
- At most one run every 10 hours per job: once or twice a day. A schedule that runs more often is refused when you publish, with one that works.
- At most 5 jobs per app, and 15 minutes per run.
- If a run is still going when the next is due, the next one is skipped.
When a job fails. A run that exits with anything but 0 is recorded as
failed, with the last lines it printed. After 5 failures in a row the job is
paused and you are emailed. Publishing again, or spry jobs resume, starts it
again.
spry jobs office-requests when each job runs, and how the last run went
spry jobs run office-requests morning-reminder run it now
spry logs office-requests --job morning-reminder its recent runs and what it printed
spry jobs resume office-requests morning-reminder after it was paused
Email to the people who use the app
email: true
The app sends a notification to Spryloom, and Spryloom emails it. Spryloom
tells the app where with SPRYLOOM_GATEWAY_URL and a key of its own with
SPRYLOOM_GATEWAY_KEY. Use the helper your coding agent adds with
spry skill, or send it yourself:
const answer = await fetch(`${process.env.SPRYLOOM_GATEWAY_URL}/v1/notify`, {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.SPRYLOOM_GATEWAY_KEY}`,
'content-type': 'application/json',
},
body: JSON.stringify({
to: 'jo@yourcompany.com',
subject: '2 requests are waiting for your approval',
text: 'Replace meeting-room monitor, and new chairs for room 4.',
button: { label: 'Review requests', path: '/approvals' },
dedupeKey: 'reminder-2026-09-22-jo',
}),
});
| Field | |
|---|---|
to |
One address: someone invited to the app, or who has signed in to it. |
subject |
Up to 100 characters. |
text |
Plain text, up to 500 characters, with no web addresses in it. Anything shaped like one is refused, including words such as "Node.js" or "report.pdf". |
button |
Optional. A label of up to 30 characters, and a path in your app, such as /requests/42. |
dedupeKey |
Optional. The same key within a day sends one email however many times you ask. |
What the email looks like. It comes from "Your app via Spryloom", from the address your invitations come from, with your address as the reply-to. Spryloom lays it out the same way every time: your subject, your text, your button, and a footer saying you wrote it, why the person is getting it, and how to stop it.
Why no links. The email comes from the same address as sign-in links. If an app could put any link in it, a message could send someone to a page that looks like Spryloom's sign-in. The button can only open a page in your app, which is behind sign-in. Anything richer belongs on that page.
What the beta allows:
- At most 5 notifications per app per day. The sixth is refused, and you are emailed once.
- Only to people who can open the app. Anyone else is refused, and the refusal says so.
- Anyone can stop an app's emails with the link in the footer. Their sign-in emails still arrive.
- If people mark an app's notifications as spam twice in 30 days, its email is stopped and you are told.
The answer to each request says what happened. 202 means it was accepted. A
refusal carries a message saying why, for example:
422 not_a_member: jo@example.com does not have access to Office requests. Invite them first, or send to someone who has.
429 daily_limit: Office requests has sent 5 notifications today, the limit during the beta. Sending starts again at midnight UTC.
Retry a 503 with the same dedupeKey: it is sent once.