Morgen Assist + OpenAI generate my daily plan + send it to my Slack. Here’s how.
Published: 2023-10-30 10:22:43

---
title: Morgen Assist + OpenAI generate my daily plan + send it to my Slack. Here’s how.
published: true
description: A guide on how to automate your calendar schedule with Morgen Assist
---

_Originally written by Luke for [morgen.so](https://morgen.so)_

This year, I had the great pleasure of joining a very exciting startup, which has a mission to revolutionise the way teams automate their time management.

Morgen recently launched a new product – [Morgen Assist](https://morgen.so) – which we think is really going to supercharge calendar automation and see 1000s of awesome workflows and apps come to market, with Morgen powering the whole thing.

But don’t take my word for it, follow along and find out how easy it is to write, test and deploy your own calendar workflows from your very own environment.

And I’m sorry but putting OpenAI in the title was the easiest way to get you to click but I promise it is necessary for this <100 line app that intelligently summarises calendar events from any-and-all of your calendars*.

* Google and O365 supported currently, CalDAV coming Soon™️

## “What am I doing today?” 

You might have wondered this question, coffee in hand, staring blankly into your remote working office having rolled out of bed and into the living room this morning. 

With 50+ new slack messages from the PM, design team, marketing and support channel, you might forget to check your calendar, which is currently lost amidst a sea of Firefox tabs, terminals and well… more terminals.

And that’s where the the Morning Schedule Workflow comes in.

A slack message is delivered every morning (or evening) straight to your DMs informing you of any upcoming meetings, including retrospectives, 1-1s, other sprint meetings, customer calls, all in the language and tone of your choice.

As a demonstration I have submitted the following text to OpenAI’s ChatGPT model:

```
You are the legendary Apple founder, Steve Jobs delivering a key note

Please summarise all of these upcoming events listed in a way typical with your
classic presentation style.

- 1-1 with David

- Retrospective on the last sprint

- Customer Call /w Apple
```


And this is what I got back:


>Ladies and gentlemen, 
>
>Today, I'm here to give you a brief update on some upcoming events that are of utmost importance to our company.
>
>First on the list, we have a 1-1 session scheduled with David. This is a fantastic opportunity for us to connect on a personal level, exchange ideas, and align our vision. Such interactions are …

And it goes on. The full keynote is truly inspiring but you’ll have to see that for yourself.

So we have the central piece of the puzzle, but how does this fit within a Morgen Assist workflow?

## Getting Started with Morgen Assist

Next up, you’re going to need an account with [Morgen Assist](https://morgen.so). You’ll only need an email address and a calendar account which is either provided by Microsoft O365 or Google.

### Creating a custom workflow

We will first need a custom workflow on the Morgen Assist platform, which you can find here.

Don’t forget to [add a calendar integration](https://platform.morgen.so/integrations/connected) to your account, you’ll need that shortly. You can ignore the slack integration for now - this is for a slightly different use-case.

Then follow these steps:

 - Set the trigger type to “timed” at a time of day of your choosing (e.g. 6pm)
 - Select a calendar to source the events to summarise
 - And finally insert code into the User Script section according to the instructions below.

:sparkles: Note that if you want that hacker 10x engineer experience, you can also write and deploy your workflow via the [SDK](https://github.com/morgen-so/morgen-cw-sdk), which has a TypeScript API client for making requests to our backend. Currently your code has to fit entirely into the `run` function, which I’ll explain below. Some interaction is still necessary in the web app, but we’re looking at improving this in future.

## Writing the Script

Thankfully, a version of this workflow has already been written and can be accessed in a form that is compatible with both the Morgen Assist SDK and the Morgen Assist Web Interface.

You can find it here: https://github.com/morgen-so/morgen-cw-sdk/blob/main/examples/openai-tomorrow-summary.ts 

I will now break this script down.

First up, we’re going to need to include a couple of utilities to make requests to Morgen and OpenAI. Paste the following into the top of your user script:

```ts
async function fetchMorgen(url, opts) {
  return fetch(
    url,
    {
      headers: {
        Authorization: `Bearer ${JSON.parse(global.TOKEN)}`,
        Accept: 'application/json',
        ["Content-Type"]: 'application/json',
        ...opts.headers,
      },
      ...opts,
    }
  );
}
async function getOpenAIResponse(prompt) {
  const resp = fetch(
    "https://api.openai.com/v1/chat/completions",
    {
      method: 'POST',
      headers: {
        Authorization: 'Bearer [YOUR-TOKEN]',
        Accepts: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        "model": "gpt-3.5-turbo",
        "messages": [{
          "role": "user",
          "content": prompt
        }],
        "temperature": 0.7
      })
    }
  )
  const parsed = JSON.parse(resp);
  if (parsed.error) {
    throw new Error('Error with OpenAI: ' + JSON.stringify(parsed.error));
    return;
  }
  return parsed.choices[0].message.content;
}
```

Note that you’ll need an OpenAI token for this to all work as expected. Once you create an OpenAI account, you can create tokens here: https://platform.openai.com/account/api-keys

Let’s get the Slack token ready as well:

 - Create a new app here: https://api.slack.com/apps
   - (this is how the summary bot will access and appear in Slack)
 - Set your app's appearance and permissions
   - Go to “OAuth & Permissions”, find the “Bot Token Scopes” section
   - Add the “chat:write” permission
 - Scroll up to the “Oauth Tokens for Your Workspace” section and click “Install to Workspace”, click “Allow” on the confirmation page
 - Find the xorb-... token under “OAuth & Permissions”

Okay, now let’s write a basic run function. This function will be called when the workflow is triggered.

```ts
async function run(trigger) {
  const slackToken = "xorb-...";
  const openAIToken = "k-...";
  // Logic goes here
}
```

Finally let’s look at the actual logic of this workflow.

## Fetching tomorrow’s events

To do this, we need to make an API call to the Morgen API with a start/end range that captures tomorrow midnight until midnight the day after.

```ts

// fetchMorgen () { ... }
// getOpenAIResponse () { ... }

async function run(trigger) {
  const slackToken = "xorb-...";
  const openAIToken = "k-...";

  // Fetch events from tomorrow

  // Get the first calendar selected in workflow config web page
  const calId = trigger.accounts.calendar[0].calendarId;

  // Set the start of the range to the start of tomorrow
  const now = luxon.DateTime.now();
  const startTs = now
    .startOf('day')
    .plus({days:1})
    .toISO({includeOffset: false, suppressMilliseconds: true});

  // Set the end of the range to the start of the day after tomorrow
  const endTs = now
    .startOf('day')
    .plus({days:2})
    .toISO({includeOffset: false, suppressMilliseconds: true});

  // Make a API request with fetchMorgen utility we added before
  const mresp = await fetchMorgen(
    `https://sync.morgen.so/v1/events/list` +
    `?calendarIds=${calId}` +
    `&start=${startTs}` +
    `&end=${endTs}`, 
    {
      method: 'GET',
    });
  const evs = JSON.parse(mresp);

  log(JSON.stringify(evs, null, 2));
}

```

If you run this, provided there are events in your selected calendar for tomorrow, you should see them listed below the editor after you click “Run & Save”.

Note that it might take a while for anything to happen, we’re working on improving this part of the custom workflows feature.

You should see a JSON blob logged that corresponds to a list of events in your calendar tomorrow. :party:

Next we’re going to send the titles of these events to OpenAI to see what it can come up with.

## ChatGPT, do your thing

Let’s take our events and get ChatGPT to summarise them in a simple format:

```ts
async function run() {
  ...
  const evs = JSON.parse(mresp);
  
  // Get all busy events and format for ChatGPT
  const list = [];
  for (let ev of evs.data.events) {
    if (ev.freeBusyStatus == "free") continue;
    list.push({
      title: ev.title,
      start: ev.start,
      duration: ev.duration,
    });
  }
  
  // ChatGPT gets confused if we don't order things chronologically
  list.sort((a,b) => a.start > b.start ? -1 : 1);

  // Generate a nice summary
  const summary = await getOpenAIResponse(
    "Summarise the following calendar events in an easily " + 
    "digestable chronological bullet list: " +
    JSON.stringify(list) +
    "\nExample: Events today:\n" + 
    "\t9:00 –\tStandup 15 minutes\n" +
    "\t12:00 –\tLunch\n" +
    "\t15:00 –\tCatchup" +
    "\nResponse: "
  );
  log(summary);
}
```

Now we have that summary, you can try running this to see that it correctly logs a nice bullet-pointed summary of events for tomorrow.

Of course, you can tweak this to be pretty much anything, for example you might want the movie trailer edition.

```ts
const summary = await getOpenAIResponse(
    "Summarise the following calendar events " +
    "in the form of a short movie trailer: " + 
    JSON.stringify(list) +
    "\nResponse: "
  );
```

And the response is hilarious as required:


>In a world filled with challenges and opportunities, a team comes together to conquer the day. Brace yourself for an epic journey through the world of corporate endeavor.
>
>In the early morning light, the team gathers for their gripping "Morning Standup". Witness the camaraderie and determination as they share their progress, align their goals, and prepare for the battles ahead. With each member showcasing their skills and expertise, it's a spectacle that will leave you inspired.
>
>But victory is not without reflection. As the sun sets and shadows deepen, our heroes find themselves in the midst of an awe-inspiring "Retrospective". Together, they venture into the past, analyzing their triumphs and challenges in order to emerge stronger than ever…

Or a poem perhaps?

>In morning's golden light, we convene,
>A team united in purpose, it seems,
>The standup whispers tales of tasks anew,
>Sharing progress, challenges, and breakthroughs.
>
>With hearts filled with hope and eyes wide awake,
>We march towards goals, no time to forsake.
>A symphony of voices, ideas flow,
>As the morning standup sparks our shared glow.
>
>But when shadows grow long, and day takes its toll,
>We gather once more, to reflect as a whole…

Brilliant. Something for everyone here with these event summaries. And of course it will always reflect the events of the day.

I will leave it as an exercise to the reader to try these modifications:

 - Ignore recurring events
 - Write a prompt to handle the case where there are no events scheduled
 - Add a description of tasks that are due to be completed today and this week

## But how do I send this to Slack?

So long as you have set up your Slack app, as described in the previous section, you should be able to start sending messages on behalf of a bot user that can be controlled with the token you copied.

To do this, use the following code:

```ts
  ... 
  
  // Send summary to Slack user
  const resp = await fetch(
    "https://api.slack.com/api/chat.postMessage", {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${slackToken}`,
      Accept: 'application/json',
      ["Content-Type"]: 'application/json',
    },
    body: JSON.stringify({
      channel: '@Luke',
      text: summary
    })
  });
}
```

Notice that I sent the message to the `“@Luke”` channel, which is how to send a direct message using the Slack API. You’ll have to change that to your username in the workspace that your app is installed under.

And with that, the workflow is complete. You can try running it, and expect it to run at the configured time in the “Trigger” section.

## Conclusion

Thanks for making it to the end of my blog, I’m really grateful for it and if you’re interested in finding out more about Morgen Assist, here are some links to get you started:

 - Find out more about how to develop your own Custom Workflows from our [Morgen Developer Documentation](https://docs.morgen.so/cw-introduction)
 - Check out our pre-alpha SDK for deploying Custom Workflows from your own environment on [GitHub](https://github.com/morgen-so/morgen-cw-sdk)
 - Come join the discussion on [Discord](https://discord.com/invite/KNaeNhZ9yf)