> For the complete documentation index, see [llms.txt](https://docs.pandium.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pandium.com/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1/write-the-integration-in-typescript/add-the-pokemonsync-flow.md).

# Add the pokemonSync flow

Send unique daily Pokémon updates from your Pandium integration by adding a pokemonSync flow that fetches Pokémon, posts Slack messages, and advances IDs using tenant metadata.

You have clients that talk to both APIs, so put them together to start sending educational Pokémon messages via Slack.

A Pandium integration can be run in normal mode or init mode. The `pokemonSync` flow is for normal mode.

The goal in this flow is to send a Slack message about a new Pokémon each day. To do this we will need to:

* [ ] Read which Pokémon was selected in the most recent run- to ensure the Pokémon in this run is a new one.
* [ ] Fetch the Pokémon for this run.
* [ ] Send a Slack message about that Pokémon to the Academy's students.

1. Add the file for this new flow:

   * [ ] Within src add a folder called processLogic.
   * [ ] Within src/processLogic add pokemonSync.ts.

   Your file structure should now look like this:

```
  ├── build 
  ├── node_modules 
  ├── src
  │  ├── processLogic
  │  │  └── pokemonSync.ts
  │  ├── index.ts
  │  └── lib.ts
  ├── package.json
  ├── PANDIUM.yaml
  └── tsconfig.json
```

2. Within src/processLogic/pokemonSync.ts get a logger for the file and add the shell of an asynchronous `pokemonSync` function.

```typescript
import log4js from 'log4js'

const logger = log4js.getLogger('pokemonSync')

export const pokemonSync = async () => {
    logger.info('------------------------POKEMON SYNC------------------------')
}
```

3. Within src/index.ts import `pokemonSync` and invoke it within the `main` function when the run mode is normal.

The src/index.ts should now look something like this:

```typescript
import * as dotenv from 'dotenv'
dotenv.config({ quiet: true })
import log4js from 'log4js'
import { WebClient } from '@slack/web-api'
import Pokedex from 'pokedex-promise-v2'
import { Pandium } from './lib.js'
import { pokemonSync } from './processLogic/pokemonSync.js'

const logger = log4js.getLogger('index')

const main = async () => {
    const pandium = Pandium.fromEnv()
    logger.info(`This run is in mode: ${pandium.runMode()}`)
    logger.info(`Tenant configs: ${JSON.stringify(pandium.config)}`)

    const pokeClient = new Pokedex()
    const slackClient = new WebClient(pandium.secrets.slack_oauth_access_token)

    if (pandium.runMode() === 'normal') {
        await pokemonSync()
    }
}

main().then(
    () => {},
    () => {
        process.exitCode = 1
    }
)
```

4. Run `pandium local build && pandium local run <tenant-id>`, and you should see the following logged:

```
[2026-09-08 10:31:05:220] [index] INFO: This run is in mode: normal
[2026-09-08 10:31:05:221] [index] INFO: Tenant configs: {}
[2026-09-08 10:31:05:221] [pokemonSync] INFO: ------------------------POKEMON SYNC------------------------
```

5. Fetch a Pokémon by doing the following:
   * [ ] In src/index.ts pass the Pokémon Client to `pokemonSync`.
   * [ ] Add `pokeClient` as an argument to `pokemonSync`.
   * [ ] Import `Pokedex` to src/processLogic/pokemonSync.ts to define the type of the `pokeClient`.
   * [ ] Declare a variable `nextPokemonId`. For now just set it to `247`.
   * [ ] Pass `nextPokemonId` to the `pokeClient.getPokemonByName`method to fetch a Pokémon by ID, because [the docs for the Pokémon library](https://www.npmjs.com/package/pokedex-promise-v2) state "Any function with the designation 'ByName' can also be passed an integer ID."
   * [ ] Log out the results of that fetch.

The pokemonSync.ts file should look something like this:

```typescript
import log4js from 'log4js'
import Pokedex from 'pokedex-promise-v2'

const logger = log4js.getLogger('pokemonSync')

export const pokemonSync = async (pokeClient: Pokedex) => {
    logger.info('------------------------POKEMON SYNC------------------------')
    const nextPokemonId = 247
    const pokemonOfTheDay = await pokeClient.getPokemonByName(nextPokemonId)
    logger.info(pokemonOfTheDay)
}
```

6. Run `pandium local build && pandium local run <tenant-id>`.

You should see the same information logged as before - except that now a large Pokémon object has also been printed. This confirms the Pokémon client within `pokemonSync` is working, so you can remove the `logger.info(pokemonOfTheDay)`.

7. Add a function to transform the `pokemonOfTheDay` into a Slack message.
   * [ ] Within src add the file transformations.ts.
   * [ ] Within transformations.ts define and export the function `pokemonToSlackMessage`.
   * [ ] Review [these Slack Web API docs ](https://api.slack.com/methods/chat.postMessage)for the postMessage endpoint and the `Pokemon` Typescript interface to fill out the `pokemonToSlackMessage` transformation function.

Here is one way transformations.ts could look:

```typescript
import { Pokemon } from 'pokedex-promise-v2'
import { ChatPostMessageArguments } from '@slack/web-api'

export const pokemonToSlackMessage = (
    pokemon: Pokemon,
    channel: string
): ChatPostMessageArguments => {
    const abilities = pokemon.abilities
        .map((ability) => ability.ability.name)
        .join(', ')
    const text = `The Pokemon of the Day is *${pokemon.name}*!
        *Abilties:* ${abilities}
        *Base Experience:* ${pokemon.base_experience}
        *Height:* ${pokemon.height}
        *Weight:* ${pokemon.weight}`
    const message: ChatPostMessageArguments = {
        channel: channel,
        text: text,
        blocks: [
            {
                type: 'section',
                text: {
                    type: 'mrkdwn',
                    text: text,
                },
            },
        ],
    }

    if (pokemon.sprites.back_default) {
        message.blocks?.unshift({
            type: 'image',
            image_url: pokemon.sprites.back_default,
            alt_text: `${pokemon.name} sprite`,
        })
    }
    return message
}
```

8. Within `pokemonSync` use `pokemonToSlackMessage` and `slackClient.chat.postMessage` to send a message to each of the Academy's Pokémon trainers.
   * [ ] Add `slackClient` as an argument of `pokemonSync` and import `WebClient` from the Slack library to define its type.
   * [ ] Within src/index.ts pass `slackClient` to `pokemonSync`.
   * [ ] Import `pokemonToSlackMessage` to pokemonSync.ts.
   * [ ] Within `pokemonSync` declare an array called `slackMemberIds`. Eventually it will hold the IDs of all the Academy's Pokémon trainers, but for development purposes just put your own Slack ID in there.
   * [ ] Pass each element of `slackMemberIds` to `pokemonToSlackMessage` to create a `slackMessage`.
   * [ ] Pass each `slackMessage` to `slackClient.chat.postMessage`.

The pokemonSync.ts file should look something like this:

```typescript
import log4js from 'log4js'
import Pokedex from 'pokedex-promise-v2'
import { WebClient } from '@slack/web-api'
import { pokemonToSlackMessage } from '../transformations.js'

const logger = log4js.getLogger('pokemonSync')

export const pokemonSync = async (
    pokeClient: Pokedex,
    slackClient: WebClient
) => {
    logger.info('------------------------POKEMON SYNC------------------------')
    const nextPokemonId = 247
    const pokemonOfTheDay = await pokeClient.getPokemonByName(nextPokemonId)

    const slackMemberIds = ['<YOUR-SLACK-MEMBER-ID>']

    for (const slackID of slackMemberIds) {
        const slackMessage = pokemonToSlackMessage(pokemonOfTheDay, slackID)
        await slackClient.chat.postMessage(slackMessage)
    }
}
```

9. Run `pandium local build && pandium local run <tenant-id>`. You should get a Slack message about the Pokémon of the Day!

We're not quite done though. If you run it again you will get another Slack message about the same Pokémon. One of the Academy's requests is that we won't repeat Pokémon.

To accomplish this goal, we will use [tenant metadata](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/tenant-metadata.md) to remember which Pokémon was sent last.

At the end of every run Pandium reads the last line the integration printed to the standard out, validates it against the `metadata_schema` you added to the PANDIUM.yaml, and merges it into the tenant's metadata. On the next run that metadata is handed back to the integration, where `pandium.metadata()` reads it.

10. Alter the integration so that it updates the tenant metadata during a normal sync.
    * [ ] `pokemonSync` should return `{ last_pokemon_id: nextPokemonId }`.
    * [ ] In src/index.ts pass the object returned by `pokemonSync` to `pandium.updateMetadata`, which prints it to the standard out.
11. Run `pandium local build && pandium local run <tenant-id>`.

You should get another slack message about that same Pokémon. However the logs now end with the metadata update, which should look something like this:

```
[2026-09-08 10:40:12:873] [lib] INFO: updating metadata with {"last_pokemon_id":247}
{"last_pokemon_id":247}
```

That last line is the standard out. When this code runs on Pandium it will be saved as the tenant's metadata.

12. Use that metadata in `pokemonSync` to ensure the Pokémon of the day is not repeated.
    * [ ] In src/index.ts pass `pandium` to `pokemonSync`.
    * [ ] In pokemonSync.ts add the argument `pandium` to `pokemonSync` and import the `Pandium` class to define the type for that new argument.
    * [ ] In `pokemonSync` add the variable `lastPokemonId`. Its value should be read from `pandium.metadata()`, and default to `0` when the tenant doesn't have any metadata yet.
    * [ ] Log out `lastPokemonId` so you can see it in the logs of each run.
    * [ ] In `pokemonSync` change `nextPokemonId` so that it will be the next number after `lastPokemonId`.

Your pokemonSync.ts should look like this:

```typescript
import log4js from 'log4js'
import Pokedex from 'pokedex-promise-v2'
import { WebClient } from '@slack/web-api'
import { pokemonToSlackMessage } from '../transformations.js'
import { Pandium } from '../lib.js'

const logger = log4js.getLogger('pokemonSync')

export const pokemonSync = async (
    pokeClient: Pokedex,
    slackClient: WebClient,
    pandium: Pandium
) => {
    logger.info('------------------------POKEMON SYNC------------------------')

    const lastPokemonId = Number(pandium.metadata()?.last_pokemon_id) || 0
    logger.info(`The last Pokémon of the day was #${lastPokemonId}`)

    const nextPokemonId = lastPokemonId + 1
    const pokemonOfTheDay = await pokeClient.getPokemonByName(nextPokemonId)

    const slackMemberIds = ['<YOUR-SLACK-MEMBER-ID>']

    for (const slackID of slackMemberIds) {
        const slackMessage = pokemonToSlackMessage(pokemonOfTheDay, slackID)
        await slackClient.chat.postMessage(slackMessage)
    }

    return { last_pokemon_id: nextPokemonId }
}
```

And your src/index.ts should look like this:

```typescript
import * as dotenv from 'dotenv'
dotenv.config({ quiet: true })
import log4js from 'log4js'
import { WebClient } from '@slack/web-api'
import Pokedex from 'pokedex-promise-v2'
import { Pandium } from './lib.js'
import { pokemonSync } from './processLogic/pokemonSync.js'

const logger = log4js.getLogger('index')

const main = async () => {
    const pandium = Pandium.fromEnv()
    logger.info(`This run is in mode: ${pandium.runMode()}`)
    logger.info(`Tenant configs: ${JSON.stringify(pandium.config)}`)

    const pokeClient = new Pokedex()
    const slackClient = new WebClient(pandium.secrets.slack_oauth_access_token)

    if (pandium.runMode() === 'normal') {
        const metadata = await pokemonSync(pokeClient, slackClient, pandium)
        pandium.updateMetadata(metadata)
    }
}

main().then(
    () => {},
    () => {
        process.exitCode = 1
    }
)
```

13. Run `pandium local build && pandium local run <tenant-id>`. You should get a Slack message about Pokémon #1, Bulbasaur!

The logs should look like this:

```
[2026-09-08 10:45:50:114] [index] INFO: This run is in mode: normal
[2026-09-08 10:45:50:115] [index] INFO: Tenant configs: {}
[2026-09-08 10:45:50:115] [pokemonSync] INFO: ------------------------POKEMON SYNC------------------------
[2026-09-08 10:45:50:116] [pokemonSync] INFO: The last Pokémon of the day was #0
[2026-09-08 10:45:51:402] [lib] INFO: updating metadata with {"last_pokemon_id":1}
{"last_pokemon_id":1}
```

Notice the following about the logs:

* Your tenant doesn't have any metadata yet, so `lastPokemonId` fell back to 0 and the Pokémon of the day was #1.
* The ID printed to the standard out is greater than the last Pokémon's ID.

If you run this locally again you will get Bulbasaur again. That's expected: a local run reads the tenant's metadata from Pandium, but it doesn't write the standard out back to Pandium. Only runs on Pandium update the tenant's metadata, which is what you will see next.

Your pokemonSync.ts should loook like the one [here](https://github.com/pandium/sample_integrations/blob/9f323d162e42ae544e918d40942a799f9179885d/POKEMON_OF_THE_DAY/src/processLogic/pokemonSync.ts). In fact, all your integration files should match all the ones in [this repository at this commit](https://github.com/pandium/sample_integrations/tree/9f323d162e42ae544e918d40942a799f9179885d/POKEMON_OF_THE_DAY).

The final step is to see what this all looks like when it is run on Pandium!


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.pandium.com/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1/write-the-integration-in-typescript/add-the-pokemonsync-flow.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
