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 all required environment variables to your Val Town environment (see table below)
- 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 | Format |
---|---|---|---|
DISCORD_BOT_TOKEN | Your Discord bot token | ā | String |
LINEAR_API_KEY | Your Linear API key | ā | String |
LINEAR_TEAM_ID | The Linear team ID where tickets should be created | ā | UUID format |
DISCORD_SERVER_ID | Your Discord server ID | ā | Discord ID |
DISCORD_MONITORED_CHANNELS | Comma-separated list of channel IDs to monitor | ā | Channel IDs separated by commas |
DISCORD_SERVER_ID=1
DISCORD_MONITORED_CHANNELS=1327384540187983926,1327384540187983927,1327384540187983928
LINEAR_TEAM_ID=6312a2a0-633b-47dc-a225-81d5d1759bf4
- Go to Discord Developer Portal
- Create a new application and bot
- Copy the bot token to
DISCORD_BOT_TOKEN
in your environment variables
- In Discord Developer Portal, go to OAuth2 ā URL Generator
- Select "bot" scope. Selecting this will unfurl a new
Bot Permissions
section below - Then select
View Channels
,Read Message History
andAdd Reactions
- Use the generated URL to invite your bot to your Discord server
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 of messages.
- Go to Linear Settings ā
Security & Access
āPersonal API Key
- Copy it to
LINEAR_API_KEY
in your environment variables
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
To get Discord server and channel IDs:
- Enable Developer Mode in Discord (Settings ā Advanced ā Developer Mode)
- Right-click on channel ā Copy ID (for
DISCORD_MONITORED_CHANNELS
) - Right-click on server name ā Copy ID (for
DISCORD_SERVER_ID
)
For multiple channels, separate them with commas:
DISCORD_MONITORED_CHANNELS=1327384540187983926,1327384540187983927
- Go to your
discord-reaction-cron.ts
file 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 and env helpers
ā āāā 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, environment helpers, and validationbackend/discord.ts
- Discord.js SDK integration with proper TypeScript typesbackend/linear.ts
- Linear SDK integration for ticket creation
Edit /backend/config.ts
to customize:
export const CONFIG = {
TARGET_EMOJI: "š§æ", // Emoji that triggers tickets (unicode character)
LINEAR_LABEL_NAME: "discord-import", // Label added to tickets
LINEAR_LABEL_COLOR: "#7C3AED", // Color for the discord-import label
LINEAR_PRIORITY: 3, // 1=Urgent, 2=High, 3=Medium, 4=Low
MAX_TITLE_LENGTH: 100, // Maximum characters in ticket title
MAX_MESSAGES_TO_CHECK: 50, // Number of recent messages to check
} as const;
Standard emojis (š§æ, ā¤ļø, š): Use the actual unicode character in config
Custom Discord emojis (custom_name
): Use just the name without colons (ie. charmaine_react instead of :charmaine_react:)
Example: When reacting with š§æ, Discord's API returns "š§æ"
(not "nazar_amulet"
), so use:
TARGET_EMOJI: "š§æ" // ā
Correct
TARGET_EMOJI: "nazar_amulet" // ā Wrong for unicode emojis
- Discord REST API (v10) - Direct API calls using fetch for maximum compatibility with Deno/Val Town
- 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 five environment variables are configured in Val Town
- Verify there are no extra spaces in the values (they are automatically trimmed)
"teamId must be a UUID" error:
- Ensure
LINEAR_TEAM_ID
is in UUID format (e.g.,6312a2a0-633b-47dc-a225-81d5d1759bf4
) - Don't use the team key (e.g., "VAL") - use the full UUID
"DISCORD_MONITORED_CHANNELS must contain at least one channel ID" error:
- Ensure the environment variable has at least one valid channel ID
- For multiple channels, separate with commas:
123,456,789
"Discord API error: 401" or "Discord API error: 403":
- Verify your
DISCORD_BOT_TOKEN
is correct and has no extra spaces - Ensure the bot has proper permissions in the Discord server
- Check that the bot is actually added to your Discord server
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
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 all channel IDs are correct in
DISCORD_MONITORED_CHANNELS
- The bot doesn't need to be online 24/7 since we use REST API, not Gateway
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_nazar_amulet
)
- 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
- Set up all environment variables - The automation will validate them on startup
The automation creates tickets with format: [Discord] <actual message content>
React to any Discord message with š§æ and watch it automatically become a Linear ticket!