SkillAgentSearch skills...

Beacon

A lightweight consent (opt-out) management platform. Handle email consent states independently from any ERP, CRM or platform. Built on .NET 10.

Install / Use

/learn @melosso/Beacon

README

🌌 Beacon

License Last commit Latest Release

This is Beacon, a lightweight consent and opt-out service built with .NET 10. Beacon manages email consent state independently of any ERP, CRM, or automation platform. It issues secure, temporary URLs for opt-out and preference changes, validates them without upstream dependencies, and exposes a simple API that other systems query before sending email.

Screenshot of Beacon

Feel free to play around in our live demo instance, available on beacon-demo.melosso.com. Use Beacon-Api-Key as your access token.

What is Beacon?

Beacon is a centralized consent and communication-preference service. It allows external systems (ERP, CRM, marketing tools, automation platforms) to store, check, and update email permission states through a single, consistent API.

Consent is organized into logical groupings called Buckets (for example: newsletters, campaigns, or customer programs). For every email address in a bucket, Beacon can generate a secure, temporary URL that lets recipients manage their own preferences. These URLs can be embedded directly into outgoing messages and validated independently using cryptographic signatures.

Beacon is designed to be:

  • Decoupled: no business logic in your sending systems
  • Stateless where possible: token validation without upstream lookups
  • Extensible: usable as a standalone service or embedded into existing flows

Beyond basic opt-out handling, Beacon also supports richer consent workflows such as signup forms, multiple permission states, and administrative management via its built-in web interface.

Noteworthy features include:

  • Token-based opt-out: Secure HMAC-signed URLs that validate without database lookups (unless you want to)
  • Multi-database support: SQLite (default), SQL Server, PostgreSQL, MySQL
  • Admin panel: Web UI for managing buckets and viewing consent records
  • Confirmation mails: You can choose for (double) opt-in confirmations via e-mail notifications
  • Form builder: Create campaign and newsletter signup forms
  • Granular permissions: Set multiple permission states in a single API call
  • Security first: Encrypted data at rest, hashed emails, rate limiting

In other words, Beacon provides a decoupled infrastructure for managing communication preferences, allowing you to externalize consent logic and opt-out processing from your primary data sources.

Getting Started

We've prepared two methods to deploy Beacon. It's up to you to choose your preferred method:

Docker Compose (Recommended)

services:
  beacon:
    image: ghcr.io/melosso/beacon:latest
    ports:
      - "5000:5000"  # Public API
      - "5001:5001"  # Admin panel
    volumes:
      - beacon_data:/app/data    # Database storage
      - beacon_core:/app/.core   # Encryption keys
    environment:
      # Core settings (required)
      - Beacon__SigningKey=${BEACON_SIGNING_KEY}
      - Beacon__EncryptionKey=${BEACON_ENCRYPTION_KEY}
      - Beacon__Pepper=${BEACON_PEPPER}
      - Beacon__AdminApiKey=${BEACON_ADMIN_API_KEY}
      - Beacon__ConnectionString=Data Source=/app/data/Beacon.db

      # Port-based routing (default, no reverse proxy)
      - Beacon__ApiPort=5000
      - Beacon__AdminPort=5001

      # Host-based routing (for reverse proxy deployments)
      # - Beacon__ApiHosts=beacon-api.example.com
      # - Beacon__AdminHosts=beacon-admin.example.com
      # - Beacon__AllowedOrigins=https://app.example.com
      # - Beacon__TrustForwardedHeaders=true

volumes:
  beacon_data:
  beacon_core:
# Create the .env file
[ -f .env ] && echo ".env already exists! Aborting." && exit 1; ADMIN_KEY=$(openssl rand -base64 48 | tr -d '\n'); ENC_KEY=$(openssl rand -base64 32); printf "BEACON_SIGNING_KEY=%s\nBEACON_ENCRYPTION_KEY=%s\nBEACON_PEPPER=%s\nBEACON_ADMIN_API_KEY=%s\n" "$(openssl rand -base64 32)" "$ENC_KEY" "$(openssl rand -base64 32)" "$ADMIN_KEY" > .env && echo "Your X-Api-Key is: $ADMIN_KEY"

# Start the container
docker compose up -d

Access the Admin panel at http://localhost:5001 and API at http://localhost:5000.

Windows Installation

Download the latest release from Releases.

  1. Install .NET 10 Runtime:
winget install --id Microsoft.DotNet.Runtime.10 -e
  1. Set encryption key:
$bytes = New-Object byte[] 48; [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes); [Environment]::SetEnvironmentVariable("BEACON_ENCRYPTION_KEY", [Convert]::ToBase64String($bytes), "Machine")
  1. Install service:
.\Beacon.bat install
.\Beacon.bat start
  1. Open browser → http://localhost:5000 / http://localhost:5001

On first run, sensitive configuration values in appsettings.json will be automatically encrypted. You should, ofcourse, safely store your API key to keep access to the admin panel too.


For production deployments with host-based routing, see the Configuration section.

How to Use

Beacon will provide you a simple, non-customizable API that does one thing: securely store permissions for an e-mail address in a bucket. Your application can use this API to create a new permission state in the bucket–and return a token. This JWT-token contains all data, allowing the user to access its data without putting load on the database:

https://beacon.acme-corporation.com/u/v1.eyJiIjoicTEtY2FtcGFpZ24iLCJlIjoia...

You can incorporate this in your newsletters, system notifications, or anything you'd like – allowing your user to configure their permissions in decentralized system and keeping them outside of your data source:

Screenshot of Permissions

API-first

As Beacon is an API-first platform, all consent management operations should be handled programmatically. While manual execution via the web UII is possible, integration typically involves automating these calls within your specific workflow. The first step requires creating a permission state for an email address in a bucket–which triggers the automatic creation of the target bucket if it is not already present.

Generate Token

Creates consent records and returns a signed opt-out token ([{"token":"<signed_jwt>"}]).

curl -X POST http://localhost:5000/api/tokens/generate \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '[{
    "bucket": "q1-campaign",
    "email": "user@example.com",
    "permissions": {
      "newsletter": true,
      "marketing": false
    }
  }]'

Response is an array of [{"token":"<signed_token>","doubleOptIn":false}]. Access the first element for single-item requests.

If you're planning on updating the permission record after insertion, may want to use configure skipPermissionUpdate to prevent overwriting (user) updated permissions.

Process Opt-Out

User clicks the token link to update preferences.

GET /u/{token}

Check Consent

Query consent status before sending email.

curl -X POST http://localhost:5000/api/consent/check \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"bucket": "q1-campaign", "email": "user@example.com", "permission": "newsletter"}'

Override Consent

curl -X POST http://localhost:5000/api/consent/override \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"bucket": "q1-campaign", "email": "user@example.com", "permission": "newsletter", "status": "OptedIn"}'

Delete Bucket

curl -X DELETE http://localhost:5000/api/admin/buckets/q1-campaign \
  -H "X-Api-Key: your-api-key"

Generate Token with Optional Features

# Supported languages: en (default), de, fr, nl, pl, es
curl -X POST http://localhost:5000/api/tokens/generate \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '[{
    "bucket": "q1-campaign",
    "email": "beige@example.com",
    "permissions": {
      "newsletter": true,
      "marketing": false
    },
    "customFields": {
      "externalId": "external-reference"
    }
    "allowReplay": false,
    "expiryDays": 30,
    "language": "nl",
    "skipPermissionUpdate": true
  }]'

Configuration

Depending on your environment, these settings are changed in your .env, docker-compose.yml or appsettings.json file.

Core Settings

| Variable | Purpose | Default | |----------|---------|---------| | Beacon__DatabaseProvider | sqlite, sqlserver, postgres, mysql | sqlite | | Beacon__ConnectionString | Database connection string | Data Source=Beacon.db | | Beacon__SigningKey | HMAC signing key (base64, 32 bytes) | Required | | Beacon__EncryptionKey | AES-256 encryption key (base64, 32 bytes) | Required | | Beacon__Pepper | Email hashing pepper | Required | | Beacon__AdminApiKey | API key for authenticated endpoints | Required | | Beacon__TokenExpiryDays | Default token validity period | 30 |

Host-Based Routing

When deploying behind a reverse proxy (nginx, Traefik, Caddy), use host-based routing to separate public API and admin traffic on different subdomains:

| Variable | Purpose | Example | |----------|---------|---------| | Beacon__ApiHosts | Hosts for public API access | beacon-api.example.com | | Beacon__AdminHosts | Hosts for admin panel access | beacon-admin.example.com | | Beacon__AllowedOrigins | Additional CORS origins

Related Skills

View on GitHub
GitHub Stars14
CategoryMarketing
Updated2d ago
Forks0

Languages

C#

Security Score

95/100

Audited on Mar 25, 2026

No findings