Hello! If you’ve been to the last couple community get-togethers on Hopin (and if you haven’t been to one yet, what are you waiting for??) you saw a demo of a secret project I’ve been working on that I’m calling Repeater. It was made to fill a hole in the Jamstack architecture, namely “how do I do something in the future?”
Use cases include:
- Sending an email
- Sending a reminder to someone at a specific date/time in the future
- Periodically doing some processing on a dataset
Right now Repeater does one thing: call to a URL at a specific datetime and record the response. That’s it. But in most cases that’s all you need for the Jamstack—you’ve got function endpoints that are available on the internet, right? If you’ve deployed to Netlify then these are called Functions. You can create your own with Redwood pretty easily.
The idea is to have your function perform whatever task you need to run. That task is triggered by the URL being hit by Repeater. You can include a body and/or headers along with your request and they’ll be available to your function.
Usage
The Repeater API is GraphQL. You’ll create/edit/delete jobs using this interface. Eventually you’ll be able to create/edit/delete jobs through the UI on the website as well, but for now you can view jobs and job results within the UI.
You’ll need to create an account at https://repeater.dev and then create an Application (you can only create an Application through the UI, no GraphQL interface for that yet). When you do, you’ll get a unique token. That token will need to be included in an Authorization
header to all GraphQL calls:
Authorization: Bearer c0d234fb00f4387c69865684afda3392
Now you can create a job with a GraphQL call to https://api.repeater.dev/graphql:
mutation {
createJob(
name: "first-job"
endpoint: "https://mysite.com/.netlify/functions/success"
verb: "get"
runAt: "2020-07-24T22:00:00Z"
body: "{\"foo\":\"bar\"}"
) {
name
}
}
From within Redwood the most secure way to create jobs would be from the server side, specifically from a Redwood service. Here’s a generic backgroundJobs
service I’m using in another project (note this requires the graphql-request
npm package which is a super-slim GraphQL client):
// api/src/services/contacts/backgroundJobs.js
import { GraphQLClient } from 'graphql-request'
const CREATE_OR_UPDATE_JOB = `
mutation CreateOrUpdateJobMutation($name: String!, $body: String!, $endpoint: String!, $runAt: String!) {
createOrUpdateJob(
name: $name,
body: $body,
endpoint: $endpoint,
runAt: $runAt,
verb: "post"
) {
name
}
}
`
const DELETE_JOB = `
mutation($name: String!) {
deleteJob(name: $name) {
name
}
}
`
const graphQLClient = new GraphQLClient('https://api.repeater.dev/graphql', {
headers: {
authorization: `Bearer ${process.env['REPEATER_APP_TOKEN']}`,
},
})
const jobName = (contact) => {
return `contact-${contact.id}-reminder`
}
export const createBackgroundJob = async (contact) => {
const variables = {
name: jobName(contact),
body: JSON.stringify({ contact: { id: contact.id } }),
endpoint: `https://mysite.com/.netlify/functions/sendReminder`,
runAt: contact.reminder.toISOString(),
}
await graphQLClient.request(CREATE_OR_UPDATE_JOB, variables)
}
export const deleteBackgroundJob = async ({ contact }) => {
const variables = { name: jobName(contact) }
await graphQLClient.request(DELETE_JOB, variables)
}
I invoke the service in my contacts
service, after a contact is created or updated in the database:
// api/src/services/contacts/contacts.js
import { createBackgroundJob, deleteBackgroundJob } from 'src/services/backgroundJobs/backgroundJobs'
import { db } from 'src/lib/db'
export const createContact = async ({ input }) => {
const contact = await db.contact.create({
data: input,
})
if (contact.reminder) {
await createBackgroundJob(contact)
}
return contact
}
export const deleteContact = async ({ id }) => {
const contact = await db.contact.findOne({where: { id }})
await deleteBackgroundJob(contact)
await db.contact.delete({where: { id }})
return contact
}
In this case I’m sending a reminder to contact someone, so I schedule the job to be sent at that reminder time via runAt
and in the body
of the job I include an object containing the ID of the contact that the owner is going to be reminded about. When the function’s URL is hit it will deserialize the body
and look up the contact’s ID and then send them the email. The function itself doesn’t care about time, it assumes it will be called when the time is right—it just sends the email.
Note that I’m using the createOrUpdate
endpoint, which will create the named job if it doesn’t exist, otherwise it’ll update an existing job with the same name. When a job is updated any outstanding background tasks are canceled and rescheduled based on the runAt
and runEvery
arguments.
You can introspect the GraphQL endpoint at https://api.repeater.dev/graphql for documentation on each endpoint. I’m working on regular HTML docs that will be available on https://repeater.dev soon! If you’re on a Mac I’ve found the Insomnia to be an excellent GUI for playing with APIs.
Documentation is available at https://docs.repeater.dev
Limitations
This probably goes without saying but this is extremely beta so please don’t use it to move money between bank accounts or manage your nuclear reactor.
- You can create a max of 100 jobs in a 24 period
- For recurring jobs the fastest they can recur is once per minute
Be aware that on the Free plan on Netlify, your functions will timeout after 10 seconds. Pro and Enterprise plans have longer timeouts. So whatever you have to do, do it fast!
TODO
I’ve got a list of features I’m working on next, including:
- Create/edit jobs through the repeater.dev UI
- Configurable timeouts per job
- Set a single job to run at multiple specific times
- Ability to set job priority
- Webhooks/notifications when a job completes (success or failure)
- Integrate into Redwood,
@redwoodjs/jobs
perhaps? - Generic npm package that anyone can import and use
I’m sure one of the first requests will be for the ability to have Repeater itself run arbitrary code for you. I hear you, and we’re thinking about it, but now we just call URLs.
Found a bug? Got a feature request?
Repeater is closed source for now but you can report issues over at the repeater-issues repo: https://github.com/redwoodjs/repeater-issues/issues