This automation creates Linear tickets from Discord messages when they receive a specific emoji reaction (🧿). It runs as a cron job every minute to check for new reactions.
- Set environment variables: Add
DISCORD_BOT_TOKEN,LINEAR_API_KEY, andLINEAR_TEAM_IDto your Val Town environment - Configure Discord bot with proper permissions and intents
- Set up the cron job to run every minute
- Start using: React to Discord messages with 🧿 to create tickets!
Set these in your Val Town environment:
| Variable | Description | Required |
|---|---|---|
DISCORD_BOT_TOKEN | Your Discord bot token | ✅ |
LINEAR_API_KEY | Your Linear API key | ✅ |
LINEAR_TEAM_ID | The Linear team ID where tickets should be created (UUID format) | ✅ |
- Go to Discord Developer Portal
- Create a new application and bot
- Copy the bot token to
DISCORD_BOT_TOKEN
Use permissions integer: 2147549248
- In Discord Developer Portal, go to OAuth2 → URL Generator
- Select "bot" scope
- Paste permissions integer:
2147549248 - Use the generated URL to invite your bot to your Discord server
This includes:
- ✅ View Channels
- ✅ Read Message History
- ✅ Add Reactions
Critical: Enable Message Content Intent
- In Discord Developer Portal, go to "Bot" → "Privileged Gateway Intents"
- Enable "Message Content Intent" ✅
Without this, your bot can see messages exist but cannot read the actual text content, resulting in empty ticket titles.
- Go to Linear Settings → API
- Create a new Personal API key
- Copy it to
LINEAR_API_KEY
Your team ID must be in UUID format (e.g., 6312a2a0-633b-47dc-a225-81d5d1759bf4).
Method 1: From Linear URL
- Go to your Linear team
- Copy the UUID from the URL:
https://linear.app/your-team/team/UUID-HERE
Method 2: Use Linear API
- Make a GraphQL query to get your teams and find the correct UUID
Edit discord-reaction-cron.ts and update these values:
const monitoredChannels = [
"1327384540187983926", // Replace with your channel ID
// Add more channel IDs as needed
];
const guildId = "1327384540187983923"; // Replace with your server ID
To get these IDs:
- Enable Developer Mode in Discord (Settings → Advanced → Developer Mode)
- Right-click on channel → Copy ID
- Right-click on server name → Copy ID
- Go to your
discord-reaction-cron.tsfile in Val Town - Set the cron schedule to run every minute:
* * * * *
- Cron job runs every minute (
discord-reaction-cron.ts) - Connects to Discord using Discord.js SDK
- Checks monitored channels for messages with target emoji reactions (🧿)
- Creates Linear tickets for new reactions with:
- Title:
[Discord] <actual message content> - Description: Full message content + link to Discord message
- Labels: "discord-import"
- Priority: Medium (configurable)
- Title:
- Prevents duplicates by tracking processed messages in Val Town blob storage
- Cleans up Discord connection after each run
├── discord-reaction-cron.ts # Main automation (runs every minute)
├── backend/
│ ├── config.ts # Configuration constants
│ ├── discord.ts # Discord.js SDK wrapper
│ └── linear.ts # Linear SDK wrapper
└── README.md
discord-reaction-cron.ts- The main automation that runs every minute to check for new reactionsbackend/config.ts- Configuration constants and environment helpersbackend/discord.ts- Discord.js SDK integration with message formattingbackend/linear.ts- Linear SDK integration for ticket creation
Edit /backend/config.ts to customize:
export const CONFIG = {
TARGET_EMOJI: "nazar_amulet", // Emoji name that triggers tickets
LINEAR_LABEL_NAME: "discord-import", // Label added to tickets
LINEAR_PRIORITY: 3, // 1=Urgent, 2=High, 3=Medium, 4=Low
MAX_TITLE_LENGTH: 100, // Maximum characters in ticket title
} as const;
- Discord.js SDK (v14.14.1) - Official Discord JavaScript SDK for reliable bot interactions
- Linear SDK (v22.0.0) - Official Linear SDK for seamless issue management
- Val Town Blob Storage - For tracking processed messages and preventing duplicates
- TypeScript - For type safety and better development experience
"Required environment variable not set" error:
- Check that all three environment variables are configured in Val Town
- Verify there are no extra spaces in the values (they are automatically trimmed)
Tickets created with "Message from username" instead of actual content:
- Enable "Message Content Intent" in Discord Developer Portal
- This is the most common issue - the bot can see messages but not read content without this intent
"teamId must be a UUID" error:
- Ensure
LINEAR_TEAM_IDis in UUID format (e.g.,6312a2a0-633b-47dc-a225-81d5d1759bf4) - Don't use the team key (e.g., "VAL") - use the full UUID
Bot not responding to reactions:
- Verify the bot is in the Discord server and has correct permissions
- Check that you're using the exact emoji name configured in
TARGET_EMOJI - Ensure the channel ID is correct in the monitored channels list
Cron job not running:
- Verify the cron schedule is set to
* * * * *in Val Town - Check Val Town logs for any startup errors
- Check Val Town logs for detailed error messages
- Verify all environment variables are set correctly
- Test with a simple message that has clear text content
- Check Discord Developer Portal for bot permissions and intents
- Check Val Town logs for cron job execution and any errors
- Monitor Linear tickets with the "discord-import" label
- Track processed messages in Val Town blob storage (keys like
processed_MESSAGE_ID_EMOJI)
- Enable Message Content Intent - This is critical for reading message content
- Use the correct permissions integer (
2147549248) when inviting the bot - Use UUID format for team ID - Not the team key
- Test with messages that have actual text content - Empty messages will show as "Message from username"
- Monitor the logs - They provide detailed information about what's happening
The automation creates tickets with format: [Discord] <actual message content>
React to any Discord message with 🧿 and watch it automatically become a Linear ticket!