Skip to content


ProLearn more about Pro

NotebooksLearn about notebooks vs. projects

Notifications let you send emails to workspace members and Slack messages to people and channels connected to a Slack workspace. Use this in combination with  Schedules to get charts, query results, and alerts sent programmatically at the frequency you configure.

You can schedule and monitor notifications from the Schedule sidebar pane. In that pane, click the battery and then Settings to see an overview of your scheduled notebooks and to connect to Slack.


This feature is in public beta. Cloud files don’t currently work in scheduled notebook runs, and our app for Slack is not yet approved in their App Directory, which limits some Slack workspaces from installing it. Contact with any questions.


To send notifications to either email or Slack, construct a new NotificationClient with the destination of your notification in the constructor. You can find snippets for this under the + add cell menu. The client’s only method is send, and it works similarly for both email and Slack.



Asynchronously returns a new client for the given destination specifier. Specifiers are of the form provider:address. Provider can be email or Slack; the address format depends on the provider.

  • For email, it can be any email address of a user in your workspace;
  • for Slack, it can be any channel the bot has been added to, a username, or a user ID.

NotificationClient only works in private notebooks.


(await NotificationClient("")).send("Hello world")

Sends one or many messages. Returns {ok: true} or an error. Each message can be:

  • a string
  • an SVG or canvas element, which will be rasterized and sent as a PNG
  • a tabular data array, which will be serialized and attached as a CSV
  • an object, which will be stringified and attached as JSON
  • a file blob, which will be attached as a file

A link back to the originating notebook is appended to the end of your messages.


(await NotificationClient(",")).send("Hello world")

Emails come from with your notebook’s title in the subject line. In the constructor, the address can be one or more comma-separated email addresses of someone in your workspace.

Text and images are sent inline; tabular data is sent as a CSV attachment; a serializable object is sent as a JSON attachment. The footer describes whether the email was sent as scheduled or manually, and, if the latter, by whom.


Send some text, a chart, and a CSV:

(await NotificationClient("")).send(
  "Here’s the latest distribution of penguin body masses",, {x: "body_mass_g"}).plot(),
Email notification with a histogram and data attached


To send messages to Slack:

  1. Click the add cell menu +, search “Slack”, and hit enter.
  2. In the Schedule sidebar pane, click “Authorize”.
  3. Add our bot to the channel you want to send to by mentioning @Observable.

All notifications will appear as coming from the Observable app.

In the constructor, the address can be:

  • a private or public channel that the bot has been added to: NotificationClient("slack:#random")
  • a conversation ID: NotificationClient("slack:C1234567890")
  • a username or user ID: NotificationClient("slack:@you")

In messages, strings are interpreted as Slack’s flavor of Markdown, mrkdwn, which is different than the flavor of Markdown on Observable. You can preview it on Observable with this helper.

If you want more control over the layout of your notification in Slack, you can pass send a single options object with a blocks property to send Slack blocks: send({blocks: […]}). See example below.


Send text:

(await NotificationClient("slack:#random")).send(`Here’s a random number: ${Math.random()}`)
Slack notification with a random number

Send a chart:

chart =, { x: "power (hp)", y: "economy (mpg)" }).plot()
(await NotificationClient("slack:#random")).send("Scatterplot of fuel economy vs. horsepower", chart)
Slack notification with a scatterplot

Send the tabular data used in that chart, which will be attached as a CSV:

(await NotificationClient("slack:#random")).send("Cars from the 1983 ASA Data Exposition:", cars)
Slack notification with a CSV attached

For something more advanced, send a chart to one channel, then send Slack blocks referencing the image to another channel:

notify = {
  const { files } = await (await NotificationClient("slack:#images")).send(chart);
  return (await NotificationClient("slack:#project-metrics")).send({
    blocks: [{
      type: "section",
      text: {type: "mrkdwn", text: `The median horsepower in the data is ${median}; the max is ${multiple}✕ that.`},
      accessory: {type: "image", image_url: files[0].url_private, alt_text: "chart"}
Slack notification with a chart beside text


We’ll always ask for confirmation before sending a notification live

Notifications are mostly meant to be used with schedules. You can send notifications from a notebook while editing it, but we’ll always ask for confirmation, since this is usually just for testing. You can choose to “Ignore requests for rest of session,” in which case we won’t ask you again and no notifications will send until you refresh or leave the page. This helps you iterate, debug, and develop notifications with the confidence that you won’t accidentally spam your company. When your notebook is running in the cloud on a schedule, we confirm automatically.

You won’t be notified if notifications fail to send

In general, an error means your notification won’t send at all. If you want to be notified of errors, you can use a try/catch pattern:

  try {
    const data = await (await fetch(path)).json();
    return (await NotificationClient("")).send(data);
  } catch (err) {
    return (await NotificationClient("")).send("Could not fetch data", err);

Remember to await or return the send promise

If your notification sends in the browser but not the scheduler, make sure you’re awaiting or returning the promise returned by send.

If you run a cell like this in the notebook in your browser, it will trigger a notification request in the browser:

  (await NotificationClient("")).send("Hello")

But, when run in the scheduler, it probably won’t send. The scheduler waits for every cell to resolve and then immediately quits running your notebook (read more); the cell above immediately returns undefined, so the notebook will quit immediately. Instead, return the promise:

  return (await NotificationClient("")).send("Hello")