A focused file-sharing dashboard for CraneMail workspace storage. It uploads files into a configured CraneMail workspace folder, publishes public links, syncs existing workspace files, and supports Telegram bot uploads for linked users.
Need an email workspace for this project? You can sign up through my NameCrane referral link.
Compatibility note: this project is developed and tested against CraneMail, but compatibility with other SmarterMail instances is not guaranteed.
- Sign in with a CraneMail account using an app session cookie while SmarterMail tokens are stored encrypted server-side.
- Upload files from the web dashboard to CraneMail workspace storage.
- Generate public links automatically after upload.
- Sync existing workspace files under
PUBLIC_FOLDER. - Delete the real CraneMail workspace file first, then remove the local record.
- Bind Telegram accounts through temporary tokens generated from the web dashboard.
- Upload photos and documents through the Telegram bot.
- List recent uploads from the Telegram bot.
- Use local SQLite in development and Turso/libSQL in production.
- Frontend built with Next.js App Router, shadcn/ui, Tailwind CSS, lucide-react, and sonner.
- Next.js 16
- React 19
- Hono API routes on Next.js
- libSQL/Turso or local SQLite
- shadcn/ui with Base UI primitives
- Tailwind CSS v4
- Telegram Bot API
- Node.js 20 or newer
- npm
- A CraneMail workspace account with file storage access
- Optional: Telegram bot token from BotFather
- Optional: Turso database credentials for hosted deployments
Install dependencies:
npm installCreate a local environment file:
cp .env.example .env.localConfigure the required values in .env.local:
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_SMARTERMAIL_URL=https://us1.workspace.org
SMARTERMAIL_CLIENT_ID=cranemail-files-app
PUBLIC_FOLDER=/public
TIMEZONE=Asia/Shanghai
ENCRYPTION_KEY=replace-with-a-secure-random-secret
TELEGRAM_BOT_TOKEN=
TELEGRAM_BOT_USERNAME=CraneMailFilesBot
TURSO_DATABASE_URL=
TURSO_AUTH_TOKEN=Use a long random value for ENCRYPTION_KEY. For example:
openssl rand -hex 32NEXT_PUBLIC_SITE_URL
Public site origin used for SEO metadata, canonical URLs, and Open Graph metadata.
NEXT_PUBLIC_SMARTERMAIL_URL
Base URL for your CraneMail workspace service. Do not include a trailing slash.
SMARTERMAIL_CLIENT_ID
Client identifier sent to the CraneMail/SmarterMail-compatible authentication endpoints.
PUBLIC_FOLDER
Workspace folder root used for uploads and sync. Both public and /public are valid. The app normalizes it to a leading slash and removes trailing slashes.
TIMEZONE
Timezone used for date-based upload folders and bot date formatting.
ENCRYPTION_KEY
Secret used to encrypt stored CraneMail passwords for Telegram bot re-authentication fallback.
TELEGRAM_BOT_TOKEN
Telegram bot token. Required only for bot upload and binding flows.
TELEGRAM_BOT_USERNAME
Telegram bot username used to generate binding links.
ALLOWED_TELEGRAM_USERS
Optional comma-separated Telegram user IDs or usernames. When set, only listed users may use the bot.
TURSO_DATABASE_URL and TURSO_AUTH_TOKEN
Optional libSQL/Turso database settings. If TURSO_DATABASE_URL is empty, the app writes to local.db in the project root.
Run the development server:
npm run devRun the Telegram bot locally in polling mode for debugging:
npm run dev:botdev:bot loads .env.local, requires TELEGRAM_BOT_TOKEN, deletes the active Telegram webhook, and then polls getUpdates. Use it for local bot debugging only, and re-register the production webhook when you are done.
Open:
http://localhost:3000
Main routes:
/- sign-in page/upload- authenticated upload dashboard/api/*- Hono API routes
Build the project:
npm run buildThis production flow assumes Vercel for hosting, Turso for the libSQL database, and Telegram Bot API webhooks for bot updates.
Choose the production domain first. It can be either a Vercel domain or your custom domain:
https://your-domain.example
Generate a stable encryption key before the first deployment:
openssl rand -hex 32Keep this value. Do not rotate ENCRYPTION_KEY casually because existing encrypted bot credentials cannot be decrypted after it changes unless users re-bind.
Open the Turso dashboard:
https://app.turso.tech
Create a database from the dashboard:
- Click
Create Database. - Name it, for example
cranemail-files. - Choose a region close to your Vercel deployment or primary users.
- Create the database.
Open the database page and copy the database URL. It should look like:
TURSO_DATABASE_URL=libsql://...Then create a database token from the dashboard:
- Open the database settings or tokens page.
- Create a new token for the app.
- Copy the token once it is shown.
Use these values for Vercel:
TURSO_DATABASE_URL=libsql://...
TURSO_AUTH_TOKEN=...The app creates the required users, app_sessions, smartermail_sessions, bind_tokens, and uploaded_files tables automatically on startup. There is no separate migration command.
In Telegram, open @BotFather and run:
/newbot
Follow BotFather's prompts, then keep:
- Bot token, used as
TELEGRAM_BOT_TOKEN - Bot username, used as
TELEGRAM_BOT_USERNAME
Example:
TELEGRAM_BOT_TOKEN=123456789:...
TELEGRAM_BOT_USERNAME=CraneMailFilesBotIf the bot should only work for specific Telegram accounts, set:
ALLOWED_TELEGRAM_USERS=123456789,some_usernameThis value accepts comma-separated Telegram numeric user IDs or usernames.
Push the repository to GitHub, then create a Vercel project from that repository.
Use these project settings:
- Framework Preset:
Next.js - Install Command:
npm install - Build Command:
npm run build - Output Directory: leave the Vercel default
Configure these Vercel environment variables for Production:
NEXT_PUBLIC_SITE_URL=https://your-domain.example
NEXT_PUBLIC_SMARTERMAIL_URL=https://us1.workspace.org
SMARTERMAIL_CLIENT_ID=cranemail-files-app
PUBLIC_FOLDER=/public
TIMEZONE=Asia/Shanghai
ENCRYPTION_KEY=your-stable-random-secret
TELEGRAM_BOT_TOKEN=123456789:...
TELEGRAM_BOT_USERNAME=CraneMailFilesBot
ALLOWED_TELEGRAM_USERS=
TURSO_DATABASE_URL=libsql://...
TURSO_AUTH_TOKEN=...Redeploy after adding or changing environment variables. Vercel deployments do not automatically pick up environment variable changes made after a deployment has already been built.
Production must use Turso. Do not rely on the local local.db fallback on Vercel because serverless filesystem state is not a durable database.
After the Vercel deployment is live, register the webhook with the production URL:
curl "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook?url=https://your-domain.example/api/telegram/webhook"Verify it:
curl "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getWebhookInfo"The response should include:
{
"ok": true,
"result": {
"url": "https://your-domain.example/api/telegram/webhook"
}
}If url is empty, points to a local tunnel, points to another deployment, or last_error_message reports delivery failures, run setWebhook again with the correct production URL.
Important: npm run dev:bot deletes the active Telegram webhook before it starts polling with getUpdates. After local bot debugging, always re-register the production webhook.
Open the production site:
https://your-domain.example
Then verify the main flows:
- Sign in with a CraneMail account.
- Open
/upload. - Upload a small file from the web dashboard.
- Generate a Telegram binding token.
- Open the generated Telegram bot link and send
/start <token>. - Confirm the bot replies with a successful binding message.
- Send a photo to the bot and confirm it returns a CraneMail public link.
In Vercel logs, Telegram activity should show requests to:
POST /api/telegram/webhook
If Telegram binding does nothing:
- Run
getWebhookInfoand confirmurlpoints to the Vercel production endpoint. - Confirm
TELEGRAM_BOT_TOKENis set in VercelProduction. - Confirm you redeployed after changing Vercel environment variables.
- Check Vercel function logs for
POST /api/telegram/webhook. - If you ran
npm run dev:bot, re-runsetWebhook.
If binding tokens are generated but Telegram says they are invalid:
- Confirm the Vercel app is using the same Turso database that created the
bind_tokensrow. - Confirm
TURSO_DATABASE_URLandTURSO_AUTH_TOKENare set inProduction. - Generate a fresh binding token; tokens expire after 10 minutes.
If the bot can bind but cannot upload:
- Confirm the user's CraneMail credentials were recently verified through the web binding flow.
- Confirm
PUBLIC_FOLDERexists or can be created by the app. - Check Vercel logs for SmarterMail API errors.
The app initializes these tables automatically:
usersapp_sessionssmartermail_sessionsbind_tokensuploaded_files
Local development uses:
local.db
Production should use Turso/libSQL by setting TURSO_DATABASE_URL and TURSO_AUTH_TOKEN.
Web uploads are stored in:
PUBLIC_FOLDER/YYYY/MM/DD
For example:
/public/2026/06/15
After upload, the app generates a public CraneMail workspace link and stores file metadata in uploaded_files.
Workspace sync scans PUBLIC_FOLDER recursively and imports files that are not already present locally by fileId or publicLink.
Deleting a file from the web dashboard performs a real CraneMail workspace file deletion first. The local database record is removed only after the workspace API reports success.
The bot supports:
/start <token>- bind Telegram to a CraneMail account- Photo or document upload - upload to CraneMail and return a public link
/list,/files, or📂 My Files- list recent uploads/helpor❓ Help- show usage help
To bind a Telegram account:
- Sign in on the web dashboard.
- Open the Telegram integration panel.
- Generate a binding token.
- Launch the bot from the generated link.
- Send files or photos to the bot.
The bot stores uploads in the same CraneMail PUBLIC_FOLDER/YYYY/MM/DD structure and records metadata in the same uploaded_files table.
- Keep
ENCRYPTION_KEY,TELEGRAM_BOT_TOKEN, and Turso credentials private. - Use
ALLOWED_TELEGRAM_USERSwhen the bot should be restricted to specific Telegram accounts. - Rotate
ENCRYPTION_KEYcarefully. Existing encrypted passwords cannot be decrypted after changing it unless you migrate or rebind users. - CraneMail workspace file deletion is destructive and cannot be undone by this app.