Slack is where most teams already talk about files. The problem is that Slack itself is a poor place to host them. Files uploaded to a free workspace disappear after 90 days. Attachments above 1GB are blocked. And shared files cannot leave the workspace without screenshots or downloads, which breaks any external collaboration. A Slack bot backed by the EasySend API solves the problem in a single slash command. The user types /share, the bot uploads to EasySend and a public download link appears in the channel.
This guide walks through the full build from creating the Slack app to deploying the webhook. It assumes you have Node.js 18 or newer and a server or serverless platform where you can run a webhook. Every line is reproducible and the EasySend API requires no key, no OAuth and no signup.
What You Will Build
A Slack slash command /share that accepts an uploaded file or a URL, posts it to EasySend over HTTPS and returns a short download link the channel can use. The bot is workspace-scoped, the message that includes the link is ephemeral by default and the entire round trip takes under two seconds for typical files. The same pattern works for Discord, Microsoft Teams and Mattermost with minor protocol changes.
Step 1: Create the Slack App
Open api.slack.com/apps in a browser and sign in with the workspace where you want to deploy the bot. Click Create New App and choose From an app manifest. App manifests let you declare permissions, slash commands and event subscriptions in a single YAML file rather than clicking through ten settings pages. Pick the target workspace and paste in the manifest from the next step.
Step 2: Paste the Manifest
The manifest below declares the bot user, the OAuth scopes, the slash command and the event subscription. Replace your-domain.example.com with the public host that will receive Slack requests once you deploy the webhook.
display_information:
name: EasySend
description: Share files outside Slack with one slash command
background_color: "#00d4ff"
features:
bot_user:
display_name: EasySend
always_online: true
slash_commands:
- command: /share
url: https://your-domain.example.com/slack/commands
description: Upload a file to EasySend and post the link
usage_hint: "[file or URL]"
should_escape: false
oauth_config:
scopes:
bot:
- chat:write
- chat:write.public
- commands
- files:read
- files:write
settings:
event_subscriptions:
request_url: https://your-domain.example.com/slack/events
bot_events:
- file_shared
- app_mention
interactivity:
is_enabled: true
request_url: https://your-domain.example.com/slack/interactive
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false
Step 3: Understand the OAuth Scopes
Each scope unlocks a specific capability. chat:write lets the bot post messages in channels it is a member of. chat:write.public extends that to public channels without explicit invites. commands enables the slash command. files:read is required to download attachments the user shared when invoking /share with a file. files:write is needed if you later want the bot to upload a thumbnail back to Slack. Avoid wildcard scopes. Slack will warn users about overly broad permissions during install and a noisy consent screen kills installs.
Step 4: Configure Event Subscriptions
Slash commands work over HTTP requests Slack sends to your webhook. Event subscriptions are separate. The bot listens for file_shared (someone dropped a file into a channel) and app_mention (someone tagged @EasySend) so the same handler can react without a slash. Slack verifies the events endpoint with a one-time URL challenge so your handler must echo back the challenge field on the first request.
Step 5: Install to Workspace and Capture the Bot Token
From the app settings page open OAuth and Permissions and click Install to Workspace. Slack walks you through the consent screen. After approval the page shows a Bot User OAuth Token that starts with xoxb-. Copy it into an environment variable named SLACK_BOT_TOKEN. Also copy the Signing Secret from the Basic Information page into SLACK_SIGNING_SECRET. The signing secret lets your webhook verify that requests really came from Slack.
Step 6: Deploy the Webhook (Node.js)
The full handler below uses the Slack Bolt framework. It validates the request signature, downloads the file Slack stored, streams it to the EasySend API and posts the resulting link back to the channel.
// server.js
import { App, ExpressReceiver } from '@slack/bolt';
import fetch from 'node-fetch';
import FormData from 'form-data';
const receiver = new ExpressReceiver({
signingSecret: process.env.SLACK_SIGNING_SECRET,
});
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
receiver,
});
async function uploadToEasySend(buffer, filename) {
const form = new FormData();
form.append('files[]', buffer, filename);
const res = await fetch('https://easysend.co/api/v1/upload', {
method: 'POST',
body: form,
headers: form.getHeaders(),
});
if (!res.ok) throw new Error('EasySend upload failed: ' + res.status);
const data = await res.json();
return 'https://easysend.co' + data.share_url;
}
app.command('/share', async ({ command, ack, respond, client }) => {
await ack();
const text = (command.text || '').trim();
if (!text) {
await respond({
response_type: 'ephemeral',
text: 'Attach a file to the channel first then run /share, or pass a URL.',
});
return;
}
try {
const fileRes = await fetch(text);
const buffer = Buffer.from(await fileRes.arrayBuffer());
const filename = text.split('/').pop() || 'file';
const link = await uploadToEasySend(buffer, filename);
await respond({
response_type: 'in_channel',
text: 'Uploaded to EasySend: ' + link,
});
} catch (err) {
await respond({
response_type: 'ephemeral',
text: 'Upload failed: ' + err.message,
});
}
});
app.event('file_shared', async ({ event, client }) => {
const info = await client.files.info({ file: event.file_id });
const fileRes = await fetch(info.file.url_private_download, {
headers: { Authorization: 'Bearer ' + process.env.SLACK_BOT_TOKEN },
});
const buffer = Buffer.from(await fileRes.arrayBuffer());
const link = await uploadToEasySend(buffer, info.file.name);
await client.chat.postMessage({
channel: event.channel_id,
text: 'Mirror posted to EasySend: ' + link,
});
});
const port = process.env.PORT || 3000;
receiver.app.listen(port, () => console.log('Slack bot on ' + port));
Install dependencies with npm i @slack/bolt node-fetch form-data and start the server with node server.js. Deploy it on Render, Fly, Railway, Cloud Run or any host that gives you a public HTTPS URL.
Step 7: Python Alternative
If your stack is Python the same flow runs in fewer lines with the Slack Bolt SDK for Python.
# app.py
import os, requests
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
from flask import Flask, request
app = App(token=os.environ['SLACK_BOT_TOKEN'],
signing_secret=os.environ['SLACK_SIGNING_SECRET'])
flask_app = Flask(__name__)
handler = SlackRequestHandler(app)
def upload(buf, name):
r = requests.post('https://easysend.co/api/v1/upload',
files={'files[]': (name, buf)})
r.raise_for_status()
return 'https://easysend.co' + r.json()['share_url']
@app.command('/share')
def share(ack, command, respond):
ack()
url = command.get('text', '').strip()
if not url:
respond('Pass a URL or attach a file then run /share.')
return
buf = requests.get(url).content
link = upload(buf, url.rsplit('/', 1)[-1] or 'file')
respond(response_type='in_channel', text='Shared: ' + link)
@flask_app.route('/slack/commands', methods=['POST'])
def commands():
return handler.handle(request)
if __name__ == '__main__':
flask_app.run(port=3000)
Step 8: Test the Bot
Invite the bot to a channel with /invite @EasySend. Run /share https://example.com/sample.pdf and confirm a link appears. Drag a file into the channel and check that the file_shared handler mirrors it to an EasySend link. If the bot is silent the most common cause is that the request URL on the slash command does not match your deployed endpoint, or the signing secret is wrong and Bolt is rejecting the request. Watch your server logs during the first test, they will tell you in plain text what went wrong.
Why EasySend for Slack Bots
- No API key, no OAuth dance and no project setup before you can upload
- One POST request returns a JSON share URL ready to post into Slack
- Files survive past Slack 90 day retention and work for external recipients with no account
- End to end encryption available with an optional password parameter for sensitive shares
- Free tier handles 1GB per file which covers the vast majority of Slack uploads
Production Hardening
Before rolling the bot out to a real workspace add a request body size limit so a 10GB upload does not crash your process, add a per user rate limit to protect your hosting bill, log the resulting EasySend short link to your application logs so you can audit shares later and consider enabling encryption with a workspace level password if your team shares anything sensitive. The API docs cover the encryption parameter, custom URLs and webhook callbacks.
Related reads: build a file upload in 10 minutes, CLI automation, install the prebuilt EasySend Slack bot.
View API Docs