Skip to content
Scava Docs Join the waitlist

Scava opens soon. These docs describe how it works at launch. Join the waitlist

All chapters

Server code

Schedules, webhooks, email and heavy work for an app

Server code can do more than answer the page. It can run on a schedule, be called by other services, send email to the people who use the app, and hand heavy jobs such as PDF reports or video work to a container. All four need the server/ folder described in Server code. The schedules and webhooks are declared in scava.json.

{
  "server": {
    "schedules": [
      { "name": "morning", "cron": "0 7 * * 1-5", "timezone": "Europe/Vienna" }
    ],
    "webhooks": ["stripe"]
  }
}

Schedules #

A schedule runs the function scheduled(event, env, ctx) of server/index.js.

export default {
  async scheduled(event, env) {
    // event: { id, name, cron, scheduledTime, attempt }
    if (event.name === 'morning') {
      const open = (await env.SCAVA.db('requests').list()).filter((e) => e.data.status === 'open');
      await env.SCAVA.mail({
        to: 'team-lead@company.com',
        subject: open.length + ' open requests',
        text: 'Good morning. There are ' + open.length + ' open requests.',
      });
    }
  },
};

At least once #

A schedule runs at each planned time while the app is live. A run that fails is tried once more a minute later. Times that pass while Scava is down are not made up afterwards. Both tries carry the same event.id; the second has event.attempt set to 2. Use the id to avoid doing the same job twice, for example by saving it in a collection.

The app page shows the last run of each schedule with its result. The owner can press "Run now" to try a schedule by hand. get_server_info shows the same to an agent.

Webhooks #

A webhook lets another service, such as a payment provider or a form tool, call your app without signing in. Declare the name in scava.json. The address is

https://<address of the app>/_scava/hook/stripe?key=<key>

If the other service signs its calls, check the signature in your code. Store that signing secret as a readable secret; see Server code.

Email #

await env.SCAVA.mail({
  to: ['anna@company.com', 'ben@company.com'],
  subject: 'Your vacation was approved',
  text: 'Your vacation from 22 December was approved.',
});
// { sent: 2, failed: 0 }

Heavy work #

Some jobs do not fit into a request: building a PDF report, converting a spreadsheet, resizing images, cutting a video. Put a script in server/heavy/ and run it from server code.

# server/heavy/report.py
import csv, json, os, sys

data = json.load(sys.stdin)            # the input arrives as JSON on stdin
os.makedirs("out", exist_ok=True)
with open("out/summary.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["name", "hours"])
    for row in data["rows"]:
        writer.writerow([row["name"], row["hours"]])
print(json.dumps({ "count": len(data["rows"]) }))   # stdout can be JSON
// in server/index.js
const r = await env.SCAVA.run('report.py', { rows: [{ name: 'Anna', hours: 38 }] }, { timeout: 60000 });
// r = { ok, exitCode, timedOut, ms, stdout, stderr, data, files: [{ name, size, base64 }] }
if (r.ok) {
  const csv = Uint8Array.from(atob(r.files[0].base64), (c) => c.charCodeAt(0));
  await env.SCAVA.files('reports').put('summary.csv', csv, { type: 'text/csv' });
}

Use heavy work for jobs that take seconds to minutes. For quick answers use the normal fetch() handler.