A self-hosted QR code scan logger with Discord notifications and email alerts. Perfect for tracking found items, personal belongings, or inventory.
- QR Code Scanning: When someone scans a QR code, their details are logged
- Geolocation: Automatically captures location data (city, region, country) from IP addresses
- Discord Notifications: Receive instant notifications on Discord when your item is found
- Email Alerts: Send email notifications to your inbox when scans occur (SMTP configurable)
- Admin Dashboard: Access
/api/scanswith your admin token to view all scan logs - Privacy-Focused: Minimal data collection, runs on your own server
- Zero Dependencies UI: Beautiful dark HTML interface, no JavaScript framework bloat
git clone https://github.com/stoneset/tagd.git tagd
cd tagd
npm installSetup environment variables:
cp .env.example .envEdit .env with your configuration:
PORT=3000
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN
ADMIN_TOKEN=your-secure-tokenCreate data/items.csv to define which items should trigger notifications:
id,name
K001,Trousseau de clés
BP001,Sac à dos rouge
LAP001,Laptop DellOnly scans from items in this CSV will trigger Discord/Email notifications. Other scans are logged but silently.
Generate QR codes pointing to:
https://your-domain.com/i/ITEM_ID
For example:
https://tagd.example.com/i/K001https://tagd.example.com/i/BP001https://tagd.example.com/i/LAP001
Short IDs (3-4 chars) keep QR codes compact and easy to scan.
Development:
npm run devProduction:
npm startServer runs on http://localhost:3000
-
GET
/- Homepage Serves the public landing page with owner contact information. -
GET
/i/:id- Scan Logger- Logs the scan with full details (IP, geolocation, user-agent, etc.)
- Sends Discord/Email notifications only if the item ID is in
data/items.csv - Serves the public page with owner contact info
- No authentication required
-
GET
/api/scans- Get Scans Returns JSON array of logged scans with optional filtering.Required Header:
x-admin-token: your-secure-tokenQuery Parameters:
itemId- Filter by item ID (e.g.,?itemId=keys-001)from- Filter from date (ISO 8601, e.g.,?from=2024-01-01)to- Filter to date (ISO 8601, e.g.,?to=2024-12-31)
Examples:
# Get all scans curl -H "x-admin-token: your-secure-token" http://localhost:3000/api/scans # Filter by item curl -H "x-admin-token: your-secure-token" http://localhost:3000/api/scans?itemId=keys-001 # Filter by date range curl -H "x-admin-token: your-secure-token" http://localhost:3000/api/scans?from=2024-01-01&to=2024-12-31
-
GET
/api/export- Export Scans Export scans in CSV or JSON format with optional filtering.Required Header:
x-admin-token: your-secure-tokenQuery Parameters:
format- Export format:csvorjson(default:json)itemId- Filter by item IDfrom- Filter from date (ISO 8601)to- Filter to date (ISO 8601)
Examples:
# Export all scans as JSON curl -H "x-admin-token: your-secure-token" http://localhost:3000/api/export > scans.json # Export all scans as CSV curl -H "x-admin-token: your-secure-token" http://localhost:3000/api/export?format=csv > scans.csv # Export scans for specific item as CSV curl -H "x-admin-token: your-secure-token" http://localhost:3000/api/export?format=csv&itemId=keys-001 > keys.csv
Each scan records:
- itemId - The item identifier from the URL
- ip - Client IP address (x-forwarded-for aware)
- userAgent - Browser/device info
- acceptLanguage - Language preferences
- referer - HTTP referer
- timestamp - ISO 8601 timestamp
- geo - Location data (city, region, country)
- headers - All HTTP request headers
When someone scans your QR code, you'll receive a Discord embed with:
- Item ID
- Visitor's IP address
- Location (city, region, country)
- Device info (user-agent)
- Scan timestamp
- Language preference
The app automatically captures location data (city, region, country) for each scan using ip-api.com.
ip-api.com:
- Accuracy: Good (city-level)
- Setup: No configuration needed
- Limitations: 45 requests/minute rate limit
All personalization is done through config.json (created from config.json.example):
"owner": {
"name": "Your Name",
"email": "your-email@example.com",
"website": "https://your-website.com"
}These fields are displayed on the public page with contact buttons.
"appearance": {
"primaryColor": "#6b7be5",
"accentColor": "#5a6bc4"
}Change colors to match your branding. The primary color is used for links, buttons, and highlights. The accent color is used for hover states.
"locales": ["en", "fr"]Add language codes to make those translation files available. Translations are loaded from public/i18n/{lang}.json.
"rateLimit": {
"enabled": true,
"windowMs": 900000,
"maxRequests": 10
}- enabled: Turn rate limiting on/off
- windowMs: Time window in milliseconds (default: 900000 = 15 minutes)
- maxRequests: Max requests per IP per window (default: 10)
Rate limiting is applied to the /i/:id endpoint to prevent abuse. When a visitor exceeds the limit, they get a 429 error.
You can send email notifications when someone scans your QR code. Configure in config.json:
"smtp": {
"enabled": false,
"host": "smtp.gmail.com",
"port": 587,
"secure": false,
"user": "your-email@gmail.com",
"pass": "your-app-password",
"from": "your-email@gmail.com",
"to": "admin@example.com"
}Configuration Fields:
- enabled: Set to
trueto enable email notifications - host: SMTP server hostname (e.g.,
smtp.gmail.com,smtp.sendgrid.net, custom server) - port: SMTP port (usually 587 for TLS or 465 for SSL)
- secure: Use SSL/TLS (false for 587, true for 465)
- user: SMTP authentication username
- pass: SMTP authentication password (use app-specific password for Gmail)
- from: Sender email address
- to: Recipient email address
Gmail Setup:
- Enable 2-factor authentication on your Google account
- Generate an app-specific password: https://myaccount.google.com/apppasswords
- Use the 16-character password in the
passfield
Custom SMTP: Use your own mail server or third-party services like Mailgun, SendGrid, or Brevo with their SMTP credentials.
- SQLite support for persistent storage
- Web UI for viewing scans
- Multiple admin users with roles
- Email notifications
- Data export (CSV, JSON)
- Map visualization of scan locations