Skip to content

melosso/beacon

Repository files navigation

🌌 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 https://app.example.com
Beacon__TrustForwardedHeaders Trust X-Forwarded-* headers from proxy true
Beacon__PublicUrl Override the base URL used in confirmation email links https://beacon-api.example.com

When using double opt-in emails, Beacon builds confirmation links using the first entry of Beacon__ApiHosts (prefixed with https://). Beacon__PublicUrl is only needed when the public URL cannot be derived from ApiHosts. So, for example, in port-based deployments without a configured hostname, or when the external URL differs from the API host (CDN, custom domain).

Port-Based Routing

When ApiHosts/AdminHosts are not configured, Beacon uses the following ports that can be overridden by changing the following variables:

Variable Purpose Default
Beacon__ApiPort Port for public API endpoints 5000
Beacon__AdminPort Port for admin panel and OpenAPI docs 5001

You may need to combine both the host- and port-based variables when working with a reverse proxy (e.g. Cloudflare Tunnels or Pangolin).

Other settings

You can configure the following additional, completely optional settings:

Variable Purpose Default
Beacon__UserAuthentication Allow user authentication, API tokens, or both (user, api, both). Leave blank to only allow Beacon__AdminApiKey both

Generating Secure Keys

# Linux/macOS
openssl rand -base64 32

# PowerShell
$b = New-Object Byte[] 32; [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b); [Convert]::ToBase64String($b)

License

Free for open source projects and personal use under the AGPL 3.0 license. For more information, please see the license file.

Contributing

Contributions are always welcome! Please submit issues and pull requests, using the templates we provided.

About

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

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors