├── .gitignore ├── package.json ├── ecosystem.config.js ├── .env.example ├── start.sh ├── README.md ├── GOOGLE_CREDENTIALS_SETUP.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /logs 3 | /token.json 4 | /credentials.json 5 | .env 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mail-forward", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "dependencies": { 13 | "@google-cloud/local-auth": "^3.0.1", 14 | "discord.js": "^14.18.0", 15 | "dotenv": "^16.4.7", 16 | "googleapis": "^148.0.0", 17 | "node-cron": "^3.0.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [{ 3 | name: 'gmail-discord-forwarder', 4 | script: 'index.js', 5 | autorestart: true, 6 | max_restarts: 10, 7 | min_uptime: '10s', 8 | restart_delay: 4000, 9 | watch: false, 10 | max_memory_restart: '1G', 11 | env: { 12 | NODE_ENV: 'production', 13 | }, 14 | error_file: 'logs/error.log', 15 | out_file: 'logs/output.log', 16 | time: true, 17 | exp_backoff_restart_delay: 100 18 | }] 19 | }; -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Gmail API credentials 2 | # Get these from Google Cloud Console > APIs & Services > Credentials 3 | # Create OAuth 2.0 Client ID (Desktop application) 4 | GMAIL_CLIENT_ID=your_client_id 5 | GMAIL_CLIENT_SECRET=your_client_secret 6 | 7 | # This is the default redirect URI for desktop applications 8 | # You don't need to run a server on this port - it's handled automatically 9 | GMAIL_REDIRECT_URI=http://localhost:3000/oauth2callback 10 | 11 | # Discord webhook URL 12 | # Get this from Discord Server Settings > Integrations > Webhooks 13 | DISCORD_WEBHOOK_URL=your_webhook_url 14 | 15 | # Email check interval (in minutes) 16 | CHECK_INTERVAL=your_interval -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Install PM2 globally if not already installed 4 | if ! command -v pm2 &> /dev/null; then 5 | echo "Installing PM2..." 6 | npm install -g pm2 7 | fi 8 | 9 | # Install dependencies if not already installed 10 | if [ ! -d "node_modules" ]; then 11 | echo "Installing dependencies..." 12 | npm install 13 | fi 14 | 15 | # Create logs directory if it doesn't exist 16 | mkdir -p logs 17 | 18 | # Start the application with PM2 19 | echo "Starting Gmail to Discord forwarder..." 20 | pm2 start ecosystem.config.js 21 | 22 | # Save PM2 process list and startup configuration 23 | pm2 save 24 | pm2 startup 25 | 26 | echo "Service started successfully!" 27 | echo "To view logs, use: pm2 logs gmail-discord-forwarder" 28 | echo "To stop service, use: pm2 stop gmail-discord-forwarder" 29 | echo "To restart service, use: pm2 restart gmail-discord-forwarder" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gmail to Discord Email Forwarder 2 | 3 | This Node.js application forwards unread Gmail emails to a Discord channel using webhooks. It runs as a background service with automatic restart capabilities. 4 | 5 | ## Prerequisites 6 | 7 | - Node.js installed 8 | - A Gmail account 9 | - A Discord server with webhook URL 10 | - Google Cloud Project with Gmail API enabled 11 | - PM2 (Process Manager) - will be installed automatically 12 | 13 | ## Setup 14 | 15 | 1. Clone this repository 16 | 2. Install dependencies: 17 | ```bash 18 | npm install 19 | ``` 20 | 21 | 3. Set up Google Cloud Project and Gmail API: 22 | - Follow the detailed instructions in [GOOGLE_CREDENTIALS_SETUP.md](GOOGLE_CREDENTIALS_SETUP.md) 23 | - This guide will walk you through: 24 | - Creating a Google Cloud Project 25 | - Enabling Gmail API 26 | - Setting up OAuth consent screen 27 | - Creating and downloading credentials 28 | - Configuring the application 29 | - **Important**: After downloading the credentials from Google Cloud Console, save them as `credentials.json` in the project root directory 30 | 31 | 4. Create a Discord webhook: 32 | - In your Discord server, go to Server Settings > Integrations > Webhooks 33 | - Create a new webhook 34 | - Copy the webhook URL 35 | 36 | 5. Configure environment variables: 37 | - Copy `.env.example` to `.env` 38 | - Fill in your Gmail API credentials and Discord webhook URL: 39 | ``` 40 | GMAIL_CLIENT_ID=your_client_id 41 | GMAIL_CLIENT_SECRET=your_client_secret 42 | DISCORD_WEBHOOK_URL=your_discord_webhook_url 43 | CHECK_INTERVAL=5 44 | ``` 45 | 46 | ## Running as a Service 47 | 48 | The application uses PM2 process manager to run as a background service with automatic restart capabilities. 49 | 50 | 1. Start the service: 51 | ```bash 52 | chmod +x start.sh 53 | ./start.sh 54 | ``` 55 | 56 | 2. Common PM2 commands: 57 | ```bash 58 | # View logs 59 | pm2 logs gmail-discord-forwarder 60 | 61 | # Monitor service 62 | pm2 monit gmail-discord-forwarder 63 | 64 | # Check status 65 | pm2 status 66 | 67 | # Stop service 68 | pm2 stop gmail-discord-forwarder 69 | 70 | # Restart service 71 | pm2 restart gmail-discord-forwarder 72 | 73 | # View error logs 74 | tail -f logs/error.log 75 | 76 | # View output logs 77 | tail -f logs/output.log 78 | ``` 79 | 80 | ## Features 81 | 82 | - Automatic email checking every 5 minutes (configurable) 83 | - Forwards unread emails to Discord channel 84 | - Marks forwarded emails as read 85 | - Auto-restart on crashes or errors 86 | - Comprehensive error logging 87 | - Memory monitoring and management 88 | - Graceful shutdown handling 89 | - Persistent authentication token management 90 | 91 | ## Configuration 92 | 93 | You can modify the following in `.env`: 94 | - `CHECK_INTERVAL`: Time in minutes between email checks (default: 5) 95 | - `GMAIL_CLIENT_ID`: Your Google Cloud OAuth client ID 96 | - `GMAIL_CLIENT_SECRET`: Your Google Cloud OAuth client secret 97 | - `DISCORD_WEBHOOK_URL`: Your Discord webhook URL 98 | 99 | Service configuration in `ecosystem.config.js`: 100 | - `max_restarts`: Maximum number of restarts (10) 101 | - `min_uptime`: Minimum uptime before considering app running (10s) 102 | - `max_memory_restart`: Restart if memory exceeds limit (1GB) 103 | - `restart_delay`: Delay between restarts (4000ms) 104 | 105 | ## Logs 106 | 107 | Logs are stored in the `logs` directory: 108 | - `error.log`: Error messages and stack traces 109 | - `output.log`: General application output 110 | 111 | ## Security Notes 112 | 113 | - Keep your `.env` file and `credentials.json` secure 114 | - Never commit these files to version control 115 | - Regularly rotate your Discord webhook URL if compromised 116 | - For detailed security best practices, see [GOOGLE_CREDENTIALS_SETUP.md](GOOGLE_CREDENTIALS_SETUP.md#security-best-practices) 117 | 118 | ## Troubleshooting 119 | 120 | If you encounter any issues: 121 | 1. Check the [Google credentials setup guide](GOOGLE_CREDENTIALS_SETUP.md#troubleshooting) for authentication issues 122 | 2. Verify your Discord webhook URL is correct 123 | 3. Ensure your Gmail account has unread emails to forward 124 | 4. Check the logs in the `logs` directory 125 | 5. Make sure `credentials.json` exists in the project root directory 126 | 6. Check PM2 status and logs for service-related issues 127 | 128 | ## Auto-start on System Boot 129 | 130 | PM2 can be configured to start the service automatically on system boot: 131 | ```bash 132 | pm2 startup 133 | pm2 save 134 | ``` -------------------------------------------------------------------------------- /GOOGLE_CREDENTIALS_SETUP.md: -------------------------------------------------------------------------------- 1 | # Setting Up Google API Credentials 2 | 3 | This guide will walk you through the process of obtaining Gmail API credentials (Client ID and Client Secret) for the Gmail to Discord forwarder. 4 | 5 | ## Step 1: Create a Google Cloud Project 6 | 7 | 1. Go to [Google Cloud Console](https://console.cloud.google.com/) 8 | 2. Click on the project dropdown at the top of the page 9 | 3. Click "New Project" 10 | 4. Enter a project name (e.g., "Gmail to Discord Forwarder") 11 | 5. Click "Create" 12 | 13 | ## Step 2: Enable the Gmail API 14 | 15 | 1. In the left sidebar, navigate to "APIs & Services" > "Library" 16 | 2. Search for "Gmail API" 17 | 3. Click on the Gmail API result 18 | 4. Click "Enable" 19 | 20 | ## Step 3: Configure OAuth Consent Screen 21 | 22 | 1. In the left sidebar, go to "APIs & Services" > "OAuth consent screen" 23 | 2. Select "External" user type (unless you have a Google Workspace organization) 24 | 3. Click "Create" 25 | 4. Fill in the required information: 26 | - App name: "Gmail to Discord Forwarder" 27 | - User support email: Your email 28 | - Developer contact information: Your email 29 | 5. Click "Save and Continue" 30 | 6. Under "Scopes", click "Add or Remove Scopes" 31 | 7. Add these scopes: 32 | - `https://www.googleapis.com/auth/gmail.readonly` 33 | - `https://www.googleapis.com/auth/gmail.modify` 34 | 8. Click "Save and Continue" 35 | 9. Under "Test users", click "Add Users" 36 | 10. Add your Gmail address 37 | 11. Click "Save and Continue" 38 | 12. Review the summary and click "Back to Dashboard" 39 | 40 | ## Step 4: Create OAuth 2.0 Credentials 41 | 42 | 1. In the left sidebar, go to "APIs & Services" > "Credentials" 43 | 2. Click "Create Credentials" > "OAuth client ID" 44 | 3. Choose "Desktop application" as the application type 45 | 4. Name: "Gmail to Discord Forwarder" 46 | 5. Click "Create" 47 | 6. You'll see a popup with your Client ID and Client Secret 48 | 7. Click "Download JSON" to save the credentials file 49 | 50 | ## Step 5: Configure the Application 51 | 52 | 1. Rename the downloaded JSON file to `credentials.json` 53 | 2. Place it in your project's root directory 54 | 3. Open the `.env` file and update these values: 55 | ``` 56 | GMAIL_CLIENT_ID=your_client_id 57 | GMAIL_CLIENT_SECRET=your_client_secret 58 | ``` 59 | 60 | ## Step 6: First-Time Authentication 61 | 62 | 1. Start the service: 63 | ```bash 64 | ./start.sh 65 | ``` 66 | 2. The first time you run the application: 67 | - It will open a browser window for authentication 68 | - Sign in with your Gmail account 69 | - Grant the requested permissions 70 | - The application will save the token for future use 71 | 72 | 3. After successful authentication: 73 | - A `token.json` file will be created 74 | - The service will continue running in the background 75 | - You can monitor it using PM2 commands 76 | 77 | ## Important Notes 78 | 79 | - Keep your credentials secure and never share them 80 | - The `credentials.json` file contains sensitive information - don't commit it to version control 81 | - The `token.json` file contains your authentication token - also keep it secure 82 | - The redirect URI (`http://localhost:3000/oauth2callback`) is handled automatically 83 | - If you're using this in production, you should: 84 | - Move your application to "Production" status in the OAuth consent screen 85 | - Add your domain to the authorized domains 86 | - Complete the OAuth consent screen verification process 87 | 88 | ## Troubleshooting 89 | 90 | If you encounter authentication issues: 91 | 1. Make sure your Gmail account is added as a test user 92 | 2. Verify that the Gmail API is enabled 93 | 3. Check that you've selected both required scopes (`gmail.readonly` and `gmail.modify`) 94 | 4. Ensure your system time is correctly set 95 | 5. Check the logs in the `logs` directory: 96 | ```bash 97 | tail -f logs/error.log 98 | ``` 99 | 6. Try deleting the `token.json` file to force re-authentication 100 | 7. Monitor the service status: 101 | ```bash 102 | pm2 status 103 | pm2 logs gmail-discord-forwarder 104 | ``` 105 | 106 | ## Security Best Practices 107 | 108 | 1. Regularly rotate your credentials 109 | 2. Use environment variables for sensitive information 110 | 3. Implement proper error handling 111 | 4. Monitor API usage and set up alerts 112 | 5. Keep your Google Cloud project secure with proper IAM settings 113 | 6. Regularly check the service logs for any suspicious activity 114 | 7. Keep PM2 and all dependencies updated 115 | 8. Use PM2's process management features to limit resource usage 116 | 9. Configure proper log rotation to manage disk space -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { google } = require('googleapis'); 3 | const { authenticate } = require('@google-cloud/local-auth'); 4 | const { WebhookClient } = require('discord.js'); 5 | const cron = require('node-cron'); 6 | const path = require('path'); 7 | const fs = require('fs'); 8 | 9 | // Create logs directory if it doesn't exist 10 | const logsDir = path.join(process.cwd(), 'logs'); 11 | if (!fs.existsSync(logsDir)) { 12 | fs.mkdirSync(logsDir); 13 | } 14 | 15 | // Error logging function 16 | function logError(error, context = '') { 17 | const timestamp = new Date().toISOString(); 18 | const errorMessage = `[${timestamp}] ${context}: ${error.message}\n${error.stack}\n`; 19 | fs.appendFileSync(path.join(logsDir, 'error.log'), errorMessage); 20 | } 21 | 22 | // Process error handling 23 | process.on('uncaughtException', (error) => { 24 | console.error('❌ Uncaught Exception:', error); 25 | logError(error, 'Uncaught Exception'); 26 | // Give time for logs to be written 27 | setTimeout(() => process.exit(1), 1000); 28 | }); 29 | 30 | process.on('unhandledRejection', (error) => { 31 | console.error('❌ Unhandled Rejection:', error); 32 | logError(error, 'Unhandled Rejection'); 33 | }); 34 | 35 | // Graceful shutdown 36 | process.on('SIGTERM', () => { 37 | console.log('👋 Received SIGTERM - Graceful shutdown...'); 38 | shutdown(); 39 | }); 40 | 41 | process.on('SIGINT', () => { 42 | console.log('👋 Received SIGINT - Graceful shutdown...'); 43 | shutdown(); 44 | }); 45 | 46 | let cronJob = null; 47 | 48 | async function shutdown() { 49 | console.log('🛑 Stopping cron job...'); 50 | if (cronJob) { 51 | cronJob.stop(); 52 | } 53 | console.log('✨ Cleanup complete. Exiting...'); 54 | process.exit(0); 55 | } 56 | 57 | // Initialize Discord webhook client 58 | console.log('🚀 Starting Gmail to Discord Email Forwarder...'); 59 | console.log('📝 Loading configuration...'); 60 | 61 | let webhookClient; 62 | try { 63 | webhookClient = new WebhookClient({ url: process.env.DISCORD_WEBHOOK_URL }); 64 | console.log('✅ Discord webhook initialized successfully'); 65 | } catch (error) { 66 | console.error('❌ Failed to initialize Discord webhook:', error.message); 67 | logError(error, 'Discord Webhook Initialization'); 68 | process.exit(1); 69 | } 70 | 71 | // Gmail API scopes 72 | const SCOPES = [ 73 | 'https://www.googleapis.com/auth/gmail.readonly', 74 | 'https://www.googleapis.com/auth/gmail.modify' // Add permission to modify emails 75 | ]; 76 | console.log('📧 Required Gmail scopes:', SCOPES); 77 | 78 | // Function to authenticate with Gmail API 79 | async function getGmailAuth() { 80 | console.log('🔐 Starting Gmail authentication process...'); 81 | try { 82 | console.log('📝 Checking for credentials.json file...'); 83 | const fs = require('fs'); 84 | 85 | // Check for credentials.json 86 | if (!fs.existsSync('credentials.json')) { 87 | throw new Error('credentials.json file not found in root directory'); 88 | } 89 | console.log('✅ credentials.json file found'); 90 | 91 | // Define token path 92 | const TOKEN_PATH = path.join(process.cwd(), 'token.json'); 93 | console.log('🔑 Token path:', TOKEN_PATH); 94 | 95 | // Check if we have stored credentials 96 | let auth = null; 97 | if (fs.existsSync(TOKEN_PATH)) { 98 | console.log('📝 Found existing token, attempting to use it...'); 99 | try { 100 | const content = fs.readFileSync(TOKEN_PATH, 'utf8'); 101 | const credentials = JSON.parse(content); 102 | 103 | // Create OAuth2 client 104 | const { client_secret, client_id, redirect_uris } = require('./credentials.json').installed; 105 | const oAuth2Client = new google.auth.OAuth2( 106 | client_id, 107 | client_secret, 108 | redirect_uris[0] 109 | ); 110 | 111 | // Set credentials 112 | oAuth2Client.setCredentials(credentials); 113 | auth = oAuth2Client; 114 | console.log('✅ Successfully loaded existing token'); 115 | } catch (err) { 116 | console.log('⚠️ Error reading or parsing token file:', err.message); 117 | // Continue to get new token if reading fails 118 | } 119 | } 120 | 121 | if (!auth) { 122 | console.log('🔑 No valid token found, starting new authentication...'); 123 | auth = await authenticate({ 124 | keyfilePath: 'credentials.json', 125 | scopes: SCOPES, 126 | // Save tokens to token.json 127 | tokens: TOKEN_PATH 128 | }); 129 | 130 | // Save the token 131 | try { 132 | fs.writeFileSync(TOKEN_PATH, JSON.stringify(auth.credentials)); 133 | console.log('✅ Token saved successfully'); 134 | } catch (err) { 135 | console.warn('⚠️ Failed to save token:', err.message); 136 | } 137 | } 138 | 139 | if (!auth) { 140 | throw new Error('Authentication returned null or undefined'); 141 | } 142 | 143 | console.log('✅ Gmail authentication successful'); 144 | return auth; 145 | } catch (error) { 146 | console.error('❌ Gmail authentication failed:', error.message); 147 | console.log('📝 Debug info:'); 148 | console.log('- Check if credentials.json exists in the root directory'); 149 | console.log('- Verify that your email is added as a test user in Google Cloud Console'); 150 | console.log('- Ensure Gmail API is enabled in Google Cloud Console'); 151 | console.log('- Full error:', error); 152 | throw error; 153 | } 154 | } 155 | 156 | // Function to get unread emails 157 | async function getUnreadEmails(auth) { 158 | console.log('🔍 Checking for unread emails...'); 159 | try { 160 | const gmail = google.gmail({ version: 'v1', auth }); 161 | const response = await gmail.users.messages.list({ 162 | userId: 'me', 163 | q: 'is:unread', 164 | }); 165 | 166 | const messages = response.data.messages || []; 167 | console.log(`📨 Found ${messages.length} unread email(s)`); 168 | return messages; 169 | } catch (error) { 170 | console.error('❌ Failed to fetch unread emails:', error.message); 171 | throw error; 172 | } 173 | } 174 | 175 | // Function to get email details 176 | async function getEmailDetails(auth, messageId) { 177 | console.log(`📩 Fetching details for email ID: ${messageId}`); 178 | try { 179 | const gmail = google.gmail({ version: 'v1', auth }); 180 | const response = await gmail.users.messages.get({ 181 | userId: 'me', 182 | id: messageId, 183 | }); 184 | 185 | const headers = response.data.payload.headers; 186 | const subject = headers.find(header => header.name === 'Subject')?.value || 'No Subject'; 187 | const from = headers.find(header => header.name === 'From')?.value || 'Unknown Sender'; 188 | 189 | // Get email body 190 | let body = ''; 191 | if (response.data.payload.parts) { 192 | const textPart = response.data.payload.parts.find(part => part.mimeType === 'text/plain'); 193 | if (textPart && textPart.body.data) { 194 | body = Buffer.from(textPart.body.data, 'base64').toString(); 195 | } 196 | } else if (response.data.payload.body.data) { 197 | body = Buffer.from(response.data.payload.body.data, 'base64').toString(); 198 | } 199 | 200 | console.log(`✅ Successfully fetched email details - Subject: ${subject}`); 201 | return { subject, from, body }; 202 | } catch (error) { 203 | console.error(`❌ Failed to fetch email details for ID ${messageId}:`, error.message); 204 | throw error; 205 | } 206 | } 207 | 208 | // Function to mark email as read 209 | async function markEmailAsRead(auth, messageId) { 210 | console.log(`📌 Marking email ${messageId} as read...`); 211 | try { 212 | const gmail = google.gmail({ version: 'v1', auth }); 213 | await gmail.users.messages.modify({ 214 | userId: 'me', 215 | id: messageId, 216 | requestBody: { 217 | removeLabelIds: ['UNREAD'], 218 | }, 219 | }); 220 | console.log(`✅ Email ${messageId} marked as read`); 221 | } catch (error) { 222 | console.error(`❌ Failed to mark email ${messageId} as read:`, error.message); 223 | throw error; 224 | } 225 | } 226 | 227 | // Function to send email to Discord 228 | async function sendToDiscord(email) { 229 | console.log(`💬 Sending email to Discord - Subject: ${email.subject}`); 230 | try { 231 | const embed = { 232 | title: email.subject, 233 | description: email.body.substring(0, 200) + (email.body.length > 200 ? '...' : ''), 234 | color: 0x0099ff, 235 | fields: [ 236 | { 237 | name: 'From', 238 | value: email.from, 239 | }, 240 | ], 241 | timestamp: new Date().toISOString(), 242 | }; 243 | 244 | await webhookClient.send({ embeds: [embed] }); 245 | console.log('✅ Email successfully sent to Discord'); 246 | } catch (error) { 247 | console.error('❌ Failed to send email to Discord:', error.message); 248 | throw error; 249 | } 250 | } 251 | 252 | // Main function to check and forward emails 253 | async function checkAndForwardEmails() { 254 | console.log('\n🔄 Starting email check cycle...'); 255 | try { 256 | const auth = await getGmailAuth(); 257 | const unreadEmails = await getUnreadEmails(auth); 258 | 259 | if (unreadEmails.length === 0) { 260 | console.log('📭 No unread emails found'); 261 | return; 262 | } 263 | 264 | for (const email of unreadEmails) { 265 | console.log(`\n📧 Processing email ID: ${email.id}`); 266 | try { 267 | const emailDetails = await getEmailDetails(auth, email.id); 268 | await sendToDiscord(emailDetails); 269 | await markEmailAsRead(auth, email.id); 270 | console.log(`✅ Successfully processed email ID: ${email.id}`); 271 | } catch (error) { 272 | console.error(`❌ Failed to process email ${email.id}:`, error.message); 273 | // Continue with next email even if one fails 274 | continue; 275 | } 276 | } 277 | } catch (error) { 278 | console.error('❌ Email check cycle failed:', error.message); 279 | } 280 | } 281 | 282 | // Modified schedule setup with error handling 283 | const interval = process.env.CHECK_INTERVAL || 5; 284 | console.log(`⏰ Setting up email check schedule: every ${interval} minutes`); 285 | 286 | try { 287 | cronJob = cron.schedule(`*/${interval} * * * *`, async () => { 288 | console.log(`\n⏰ Running scheduled check at ${new Date().toLocaleString()}`); 289 | try { 290 | await checkAndForwardEmails(); 291 | } catch (error) { 292 | console.error('❌ Scheduled check failed:', error); 293 | logError(error, 'Scheduled Check'); 294 | } 295 | }); 296 | 297 | // Initial check on startup with error handling 298 | console.log('🏃 Running initial email check...'); 299 | checkAndForwardEmails().catch(error => { 300 | console.error('❌ Initial check failed:', error); 301 | logError(error, 'Initial Check'); 302 | }); 303 | 304 | console.log(`\n✨ Email forwarding service started successfully!`); 305 | console.log(`📋 Configuration:`); 306 | console.log(`- Check interval: ${interval} minutes`); 307 | console.log(`- Gmail scope: ${SCOPES.join(', ')}`); 308 | console.log('- Discord webhook: Configured'); 309 | console.log(`- Logs directory: ${logsDir}`); 310 | console.log('\n📝 Logs will appear below as emails are processed...'); 311 | } catch (error) { 312 | console.error('❌ Failed to start service:', error); 313 | logError(error, 'Service Startup'); 314 | process.exit(1); 315 | } --------------------------------------------------------------------------------