Skip to main content

Serverless Discord interactions framework for AWS Lambda

Project description

📡 Cordless

A serverless Discord interactions framework for AWS Lambda

Cordless lets you build Discord bots without running a server — just functions, deployed to Lambda.

  • No WebSockets
  • No stateful runtime
  • No gateway sharding

Just HTTP → functions → responses.

✨ Why Cordless?

Traditional Discord bots require:

  • persistent servers
  • WebSocket connections
  • intent configuration
  • runtime state management

Cordless flips that model:

Discord sends events → AWS Lambda runs your code → you return a response

⚡ Core Idea

Discord Interaction
      │
      ▼
API Gateway
      │
      ▼
 AWS Lambda
      │
      ▼
Cordless Router
      │
      ▼
 Your Functions
      │
      ▼
JSON Response back to Discord

🚀 Quickstart

Install

pip install cordless

Create your first bot

from cordless import Cordless

bot = Cordless()

@bot.command("ping")
async def ping(ctx):
    await ctx.send("pong")

Lambda entry point

import os
from cordless import Cordless

bot = Cordless(public_key=os.environ["DISCORD_PUBLIC_KEY"])

@bot.command("ping")
async def ping(ctx):
    await ctx.send("pong")

def handler(event, context):
    return bot.handle(event)

🔒 Request verification

Every request Discord sends to your endpoint is signed with Ed25519. Pass your application's public key (from the Discord Developer Portal) to Cordless() and every incoming request is verified before your handlers ever run — requests with a missing or invalid signature are rejected with 401 and never reach your code.

bot = Cordless(public_key=os.environ["DISCORD_PUBLIC_KEY"])

PING interactions, which Discord sends when you first configure your endpoint URL, are answered automatically.

Omitting public_key skips verification — useful for local testing, but never deploy without it: anyone who finds your Lambda URL could otherwise forge interactions.

🗒️ Registering commands with Discord

@bot.command(...) only wires up local dispatch — Discord also needs to know your commands exist so it can show them in the client. Give each command a description (and options, if it takes arguments):

@bot.command(
    "echo",
    description="Repeats what you say",
    options=[
        {"name": "text", "description": "Text to repeat", "type": 3, "required": True},
    ],
)
async def echo(ctx):
    await ctx.send(ctx.options["text"])

Then sync them to Discord with the cordless CLI (installed alongside the package) — run this once after deploying, and again whenever a command's shape changes. Point it at MODULE:ATTRIBUTE, wherever your Cordless() instance lives:

export DISCORD_BOT_TOKEN=...

cordless register app:bot                       # global — every authorized guild, every user
cordless register app:bot --guild-id 123456789   # a single guild, for instant updates while developing

The application id is resolved from the bot token, so that's all you need to provide. Omit --guild-id to register globally; global commands can take up to an hour to propagate, so use --guild-id while iterating.

No bot token? Use client credentials instead

If your app never needs a bot user — it only ever responds to HTTP interactions, like everything cordless does — you don't need a bot token at all. Discord also accepts an OAuth2 client credentials grant for managing commands, authenticated with just your app's client ID and secret (from the Developer Portal's OAuth2 page):

export DISCORD_CLIENT_ID=...
export DISCORD_CLIENT_SECRET=...

cordless register app:bot

If both a bot token and client credentials are available, the bot token takes precedence.

Prefer calling it from code instead (e.g. inside a deploy script)? Use bot.sync_commands(bot_token=..., guild_id=...) or bot.sync_commands(client_id=..., client_secret=..., guild_id=...) directly — it's what the CLI calls under the hood.

Command arguments show up on ctx.options as a plain dict, e.g. ctx.options["text"].

🧩 Commands & Interactivity

Commands

@bot.command("hello")
async def hello(ctx):
    await ctx.send("Hello world!")

Buttons

Note: the @bot.button(...) decorator below works today. Sending button components (the components= argument and cordless.ui.Button class) is still in active development.

Send a button:

from cordless.ui import Button

@bot.command("ping")
async def ping(ctx):
    await ctx.send(
        "pong",
        components=[
            Button(label="Edit", custom_id="edit_ping")
        ]
    )

Handle button clicks:

@bot.button("edit_ping")
async def edit_ping(ctx):
    await ctx.edit("edited")

🧠 Key concepts

Stateless by design:

  • interaction payload
  • custom_id routing
  • Lambda invocation context

No WebSocket required.

📦 Architecture

src/cordless/
├── __init__.py
├── app.py
├── router.py
├── context.py
├── verify.py
├── register.py
├── errors.py
└── response/
    └── responder.py

💡 Philosophy

Cordless is built around one idea:

Discord apps should feel like serverless functions, not servers.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cordless-0.2.0.tar.gz (13.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cordless-0.2.0-py3-none-any.whl (10.5 kB view details)

Uploaded Python 3

File details

Details for the file cordless-0.2.0.tar.gz.

File metadata

  • Download URL: cordless-0.2.0.tar.gz
  • Upload date:
  • Size: 13.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for cordless-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b920dc64f6be0837245af08faa89d6ca65c255ade5a20c6860b86a72c33eac3e
MD5 71769f754639bd85cfa5bf415fb082e9
BLAKE2b-256 96c5bd653127c0debbdeca0256425fac768eaf32b7e038efcd51ae304d80b505

See more details on using hashes here.

File details

Details for the file cordless-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: cordless-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 10.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for cordless-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7db56e0a7b06af13dd0be3449579e188783358e1d48d4ac0ef201bcbfdde6fa0
MD5 567c26e4be3c9d51c7a3c90ee9932695
BLAKE2b-256 f7ad44a56695ef479225f4b4a187dfd372cfb66e19466c26d8b8c7d288ca1977

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page