On February 24th, 2022, the lives of the entire Ukrainian nation were disrupted and thrown into uncertainty because of the unprovoked, illegal invasion by the Russian Federation. As a result, millions have been dislocated and are at risk within war zones throughout Ukraine.
Ukrainian businesses have become key in the resistance – providing aid to employees, coordinating humanitarian aid nation-wide, and contributing to anti-war initiatives, all while trying to keep their organizations viable.
Together App aims to help organizations be tight-knit communities for their members. It’s a simple way to know where your people are, to know when and how to help each other, and to have moments of real human connection when they are most needed.
After all, we are in this together.
Deploying Together App
·
Customization
·
Open a GitHub Issue
In times of crisis, people move around quickly, connection gets disrupted, and safety conditions change rapidly. Every day, the Together App Slack bot asks members to check in – providing information about their whereabouts, wellbeing, and their ability to contribute to team efforts.
The check-in only takes seconds but it provides a clear, actionable overview of organization members for the wellbeing stakeholders. Nobody is left behind and everybody is on the same page.
Together App’s check-in requests are sent to all members via its Slack bot. We use Slack because it’s a trusted and reachable tool for companies worldwide.
Additionally, the application never stores a member’s exact location to avoid collecting sensitive data like a refugee camp or bomb shelter in a city under attack. It stores only the coordinates of the actual city in which the member is located.
These protective measures are crucial now that many Ukrainian companies are withstanding constant cyber threats coming from Russia. With Together App, your company’s and your people’s data is safe.
In addition to providing a dashboard accessible for wellbeing stakeholders, Together App puts every team member on an interactive map. Using the map, members can see who is staying in the same city, meet up, and help each other.
The map also became crucial for coordinating the logistics of humanitarian efforts and connecting members worldwide. Plus, it just feels good to see everybody at the same time, on the same map.
- Automatically request wellbeing check-ins from organization members on a regular basis.
- Allow members to check in other peers who may not have connectivity if they know their current status.
- Repeat the previous wellbeing check-in in two taps if conditions haven't changed.
- Monitor member wellbeing, sending notifications to wellbeing stakeholders (HR, direct managers, etc.) when a member is at risk.
- Keep track of who has been mobilized.
- Display check-in data for members in its thorough dashboard.
- Allow you to control who can see what data in the dashboard.
- Assist in building reports and queries based on check-in data for assisting members (i.e., "show me all of the members in Mykolaiv to help me coordinate an evacuation").
- Display an interactive map of an organization's members, allowing members to find help, community, and support in their current location.
- It respects privacy – members can opt-out of being displayed on the map, be exempt from checking in, and Together App will never save the exact location of any member. Although it uses browser geolocation to make the check-in process fast and effortless, only the coordinates of the member's city are stored in the database.
Together App has two key components:
- The Together App Slack bot.
- The Together App dashboard.
Aside from automatically requesting check-ins, members can proactively check in using the /together
slash command and the Together App global shortcuts.
Upon deploying your self-hosted version of Together App, a dashboard will be available on the domain, where you and other organization members can view members and their check-in data, as well as the interactive map.
Actual check-in data is limited to Together App administrators (which can be added in the dashboard), while the map is visible to all members of the organization who have access through your SSO or OAuth provider.
Below is an in-depth guide on how you can deploy your own instance of Together App for your organization. This is intended to take no longer than an hour.
The following guide assumes that your organization already has:
- A Slack workspace.
- A single sign on (SSO) or OAuth provider (Okta, Google, etc.).
If you do not, you will need to set these up in order to use Together App.
This guide will walk you through deploying Together App on Heroku. However, almost all the steps here are globally true for and easily applied to any hosting.
Below is the list of required environment variables. Depending on where you are deploying Together App, these may need to be set either in the .env
file or elsewhere (i.e. the Heroku dashboard).
This deployment guide will walk you through populating the values of the environment variables and will explain which each is used for.
NODE_ENV=
PORT=
HOST=
DATABASE_URL=
SLACK_TOGETHER_APP_TOKEN=
SLACK_TOGETHER_APP_SIGNING_SECRET=
SLACK_MONITORING_CHANNEL_ID=
SLACK_ORGANIZATION_CHANNEL_ID=
SLACK_WORKSPACE_ID=
TOGETHER_ADMINISTRATOR_SLACK_USER_ID=
GOOGLE_GEOCODING_API_TOKEN=
GOOGLE_PLACES_API_TOKEN=
MAPBOX_MAP_TOKEN=
OKTA_CLIENT_ID=
OKTA_CLIENT_SECRET=
OKTA_ISSUER=
NEXTAUTH_URL=
NEXTAUTH_SECRET=
ALLOWED_REFERRER_ID=
JOBS_API_TOKEN=
The easiest way to get started with Together App is to deploy it to Heroku.
- Create a new Heroku application according to the documentation.
- Since Together App relies on Slack, due to response time allowances for Slack's events, it is recommended that you purchase a Hobby plan, so that the app does not sleep.
- Head to the Settings tab and, if needed, set up your custom domain (SSL and a Heroku domain are provided for free).
- There, in the Settings tab, click Reveal Config Vars to set up the environment. This is where you'll be setting all of the environment variables.
- Set the
HOST
andNEXTAUTH_URL
to your custom or Heroku domain. - Set the
NODE_ENV
toproduction
. - Make sure that you do not set the
PORT
environment variable, as Heroku application ports are dynamic.
Together App requires a PostgreSQL database.
- If you are working with Heroku, provision the Heroku Postgres add-on.
- Once you do that, the
DATABASE_URL
environment variable is set for you. - However, if you are not using Heroku Postgres, you will need to set the
DATABASE_URL
environment variable.
Together App integrates with Slack. It is there that your organization's members will receive check in requests and where member wellbeing stakeholders will receive notifications. The reasoning behind using Slack is that it is the most popular collaboration tool for organizations and is generally a very safe, trusted environment, which is important in light of currently heightened worries of phishing and other cyber attacks.
To create your Slack application:
- Copy the YML contents below to your clipboard (note the this is also available in the
manifest.yml
file in theslack
folder. - Make sure you replace the
{{host}}
variable with the domain on which you'll be hosting Together App. - Follow the instructions laid out by Slack to install an app via an app manifest.
- Go to the app's Basic Information page, scroll to the bottom, and upload the app's icon. You can find the default Together App icon in the
slack
folder. - Here, on the Basic Information page, click Install to Workspace.
- Once it has been installed, copy the Signing Secret from the app's Basic Information page to the
SLACK_TOGETHER_APP_SIGNING_SECRET
. - Go to the OAuth & Permissions page and copy the Bot User OAuth Token to the
SLACK_TOGETHER_APP_TOKEN
environment variable.
display_information:
name: Together App
description: An app to help us stay together during these tough times.
background_color: "#36373d"
features:
bot_user:
display_name: Together App
always_online: true
shortcuts:
- name: Check In Someone Else
type: global
callback_id: renderCheckInOtherMemberConfirmation
description: Check in another member of your organization with Together App.
- name: Check In
type: global
callback_id: renderCheckInSelfConfirmation
description: Check in using Together App.
- name: Repeat Last Check In
type: global
callback_id: renderRepeatCheckInConfirmation
description: Repeat your last check in with Together App.
slash_commands:
- command: /together
url: https://{{host}}/api/slack/command
description: Call the Together App menu to check in.
should_escape: false
oauth_config:
scopes:
bot:
- commands
- users:read
- users:read.email
- chat:write
settings:
interactivity:
is_enabled: true
request_url: https://{{host}}/api/slack/action
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false
There is some other information from your Slack workspace that Together App needs in order to function.
Namely:
- The Team ID of your Slack workspace.
- The Channel ID of an organization-wide channel where check in requests will be sent.
- The Channel ID of a channel where Together App can send notifications to member wellbeing stakeholders.
- The User ID of the initial member who will have administrator privileges in Together App.
To get this information:
- Install the Slack Developer Tools application from the Slack App Directory.
- Inside of the Slack client, open the Shortcuts Menu, and search for and click Display IDs.
- In the modal that opens, copy the value of Your Workspace ID to the
SLACK_WORKSPACE_ID
environment variable. - In the same modal, find the member who will have initial administrator privileges and copy their ID to the
TOGETHER_ADMINISTRATOR_SLACK_USER_ID
environment variable. - In the same modal, find the channel to which Together App should send organizational-wide check in requests and copy the ID to the
SLACK_ORGANIZATION_CHANNEL_ID
environment variable. - Do the same to find the channel ID of the channel to which Together App can send notifications to member wellbeing stakeholders and copy the ID to the
SLACK_MONITORING_CHANNEL_ID
environment variable.
Together App uses two Google APIs to make the check in process as easy as possible – the Geocoding API and the Places API. You need to create API tokens for both.
Follow these steps:
- Create an API key for the Geocoding API according to Google's documentation.
- Create an API key for the Places API according to Google's documentation.
- Since the Places API key is exposed to the client, you'll want to restrict it by HTTP Referrer.
- Copy the Geocoding API key to the
GOOGLE_GEOCODING_API_TOKEN
environment variable. - Copy the Places API key to the
GOOGLE_PLACES_API_TOKEN
environment variable.
Together App uses MapBox to render the interactive map of an organization's members.
Follow these steps:
- Create an access token (that starts with
pk
) for MapBox according to their documentation. - Since the access token is exposed to the client, you'll want to restrict it by URL.
- Copy the MapBox access token to the
MAPBOX_MAP_TOKEN
environment variable.
Out of the box, Together App supports signing in with Okta as the main SSO provider. However, the app is built with NextJS, and the package NextAuth supports multiple SSO and OAuth providers.
In this section are the instructions for setting up Okta authentication. View the section on customizations to learn how to set up a custom provider.
To set up Okta authentication:
- Create an OIDC custom app integration for Together App.
- Set the redirect URL for the app to be
https://{{host}}/api/auth/callback/okta
. - Find your app's credentials, and copy in the Client ID to the
OKTA_CLIENT_ID
environment variable. - Copy the Client Secret to the
OKTA_CLIENT_SECRET
environment variable. - Copy your organization's Okta domain to the
OKTA_ISSUER
environment variable. - Provision access to your organization's members as needed. Note that priveleges are handled inside of the Together App and managed in the dashboard – not through Okta groups.
There are additional secrets that are needed for Together App to function. Each one is a random string. You can use key generators to create them:
NEXTAUTH_SECRET
– a random string used to hash tokens, sign/encrypt cookies and generate cryptographic keys.ALLOWED_REFERRER_ID
– random string used to verify whether or not to render the check in page when redirected from Slack (other protection methods are in place, too).JOBS_API_TOKEN
– a random string used to verify whether or not a request to thehttps://{{host}}/api/jobs/*
endpoints are valid. These endpoints are used for scheduled jobs and do not return any data.
Now that the environment has been populated, you're ready to deploy.
First, we need to deploy the code to the Heroku app:
- Head over to the Deploy tab of your Heroku app.
- Configure GitHub integration.
- Select the forked repository in your GitHub account.
- Click Deploy branch.
- At this point, you will also want to install the Heroku CLI.
To view the build and deploy progress, open your Terminal and type in:
heroku logs -t -a {{heroku-app-name}}
Before you can use Together App, you'll need to initiate the database scheme and seed data.
First, open the command line within your Heroku application:
heroku run bash -a {{heroku-app-name}}
Then, to update the schema, run the following command inside the application:
npm run migrate:production
Then, you need to seed the data:
npm run initialize
This command syncs the database with your Slack workspace and provides administrator permissions to the member whose Slack ID was set in the TOGETHER_ADMINISTRATOR_SLACK_USER_ID
environment variable.
Together App relies on scheduled jobs for syncing users with your Slack workspace, sending check in requests, reminding members to check in, and notifying stakeholders about members who haven't checked in recently.
These are invoked calling the following scripts:
npm run sync
– this syncs the Together App member base with your Slack workspace, importing newly added members and updating those with status changes (deleted, restricted, etc.).npm run request
– this sends a check-in request message to the organization-wide channel and as a direct message to each member.npm run remind
– this sends a check in reminder for members who haven't checked within a certain number of hours. This defaults to 24 hours, but is configurable.npm run notify
– this sends a notification to the monitoring channel for members who haven't checked in within a certain number of hours. This defaults to 24 hours, but is configurable.
To set these up on Heroku:
- For the app you created above, provision the free Heroku Scheduler add-on.
- Navigate to your app's Overview tab, and go to the Heroku Scheduler settings.
- Click Add Job.
- Choose a time interval and paste in the command from above.
Note that it is up to you how often and when to call these scripts. You might want to just request check-ins once per week. Aside from that, there are other customizations available, too.
An example algorithm for daily check-ins might be:
- 8:00 AM
npm run sync
- 11:00 AM
npm run request
- 14:00 PM
npm run remind
- 16:00 PM
npm run notify
- 11:00 PM
npm run sync
This will:
- Keep your member database up to date.
- Request that members check in towards the start of the business day.
- When members don't check in within three hours, remind them.
- Two hours later, notify stakeholders of members who are potentially off the grid or at risk.
But it's completely up to you and your organization. You will, however, want to call the npm run sync
script at least once on a daily basis, at a time when most of your members are not active.
Together App comes with a docker-compose.yml
file to make it easy to deploy locally. Overall, most of the steps above apply to deploying locally. There are just a few changes needed to do so.
- Make sure that Docker is installed.
- Set up the environment variables in a
.env
file in the project's root. - Set the
NODE_ENV
environment variable tolocal
. - Set the
DATABASE_URL
environment variable topostgres://admin:admin@db:5432/together
. - Set the
PORT
environment variable to3000
. - You'll need to use NGROK to proxy requests to the local host, since Slack requests are proxied from the client to the server. You can read more about this here.
- Set the
HOST
andNEXTAUTH_URL
environment variables to the NGROK host. - You'll need to create a second Slack application, changing the host to the NGROK host in the provided URLs, and changing the name of the slash command.
Once you've set up the environment, open the Terminal and run the following commands.
To install dependencies:
npm install
To build the project:
npm run build
To start up the server:
docker-compose up
To sync the database schema, you first need to navigate to the container:
docker exec -it together sh
Then run the migrations:
npm run migrate:local
The seed data and initialize the app:
npm run initialize
The same goes with scheduled tasks – they need to run inside of the Docker container.
Together App has a few ways to customize the business logic for your instance. There are configuration files with type definitions location in config/custom
that you can directly edit – the content of these files will not change with further releases.
Authentication is implemented using NextAuth, so Together App supports any of the authentication providers supported by NextAuth.
To use a provider other than the default Okta, you can add it to config/custom/auth-provider-config.ts
:
import type { Nullable, AuthProviderConfig } from '../../types';
import SlackProvider from 'next-auth/providers/slack';
const SLACK_CLIENT_ID = process.env.SLACK_CLIENT_ID!;
const SLACK_CLIENT_SECRET = process.env.SLACK_CLIENT_SECRET!;
export const authProviderConfig: Nullable<AuthProviderConfig> = {
provider: SlackProvider({
clientId: SLACK_CLIENT_ID,
clientSecret: SLACK_CLIENT_SECRET,
}),
type: 'slack',
};
By default, members that are either restricted or ultra-restricted in your Slack workspace will not be included in check-in requests, the dashboard, or the map. They won't be able to check in or access data.
This can be configured by modifying config/custom/filter-slack-member-rule.ts
:
import type { Nullable, FilterSlackMemberRule } from '../../types';
export const filterSlackMemberRule: Nullable<FilterSlackMemberRule> = {
filterRestricted: false, // Restricted members will also be in Together App
filterUltraRestricted: true,
};
By default, when the scheduled job npm run request
is invoked, a request is sent to both the organization-wide channel and to each member as a direct message, from the Slack app.
This can be configured by modifying config/custom/check-in-request-rule.ts
:
import type { Nullable, CheckInRequestRule } from '../../types';
export const checkInRequestRule: Nullable<CheckInRequestRule> = {
requestCheckInDirectMessage: false, // The message will not be sent as a direct message
requestCheckInOrganizationChannel: true,
};
By default, when running the npm run notify
scheduled job, Together App notifies you in the monitoring channel about members that haven't checked in within the 24 hours previous to the job being invoked.
This can be configured by modifying config/custom/notify-if-not-checked-in-within-rule.ts
:
import type { Nullable, NotifyOfLateCheckInRule } from '../../types';
export const notifyIfNotCheckedInWithinRule: Nullable<NotifyOfLateCheckInRule> = {
hours: 12, // Will notify stakeholders of members not checked in within last 12 hours
};
By default, when running the npm run remind
scheduled job, Together App queries members who have not checked in within the last 24 hours and sends each one a reminder, requesting a check in.
This can be configured by modifying config/custom/remind-if-not-checked-in-within-rule.ts
:
import type { Nullable, RemindMemberOfLateCheckInRule } from '../../types';
export const remindIfNotCheckedInWithinRule: Nullable<RemindMemberOfLateCheckInRule> = {
hours: 48, // Will remind members not checked in within last 48 hours to check in
};
Every time a member checks in Together App determines whether or not the member is at risk or in need of assistance, and sends a notification to the monitoring channel.
By default, it considers a member at risk if they meet one of the following criteria:
- Have checked themselves in as not being in a safe place
- Have been mobilized
- Have not checked in within the past 24 hours
This can be configured by declaring a function in config/custom/member-is-at-risk-rule.ts
:
import type { Nullable, MemberIsAtRiskRule } from '../../types';
import type { Member } from '../../entities';
export const memberIsAtRiskRule: Nullable<MemberIsAtRiskRule> = (member: Member): boolean => {
const isSafe = member.checkIn && member.checkIn.isSafe;
const isNotMobilized = !member.isMobilized;
return !Boolean(isSafe && isNotMobilized); // No check in within 24 hours is no longer considered at risk
};
By default, Together App uses console.log()
for errors. You can also declare your own logger that complies to the provided Logger
interface:
import type { Logger } from '@slack/logger';
import type { Nullable } from '../../types';
import SomeLogger from '../../logger';
export const logger: Nullable<Logger> = new SomeLogger();
Ray East (@raycharius)
Vadym Grabovyi (@hraboviyvadim)
Boris Zagorodniy (@BZahorodnii)
Taras Neporozhniy (@korywka)
Alexey Chernyshov (@ft502)