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.1.tar.gz (13.9 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.1-py3-none-any.whl (11.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: cordless-0.2.1.tar.gz
  • Upload date:
  • Size: 13.9 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.1.tar.gz
Algorithm Hash digest
SHA256 9c69014b1a6ffaff3dc6d1ca5df0df1dbf657ea39bc65e8d529a314b70acfb35
MD5 f821943c1ebba8776ebd89e46b963b08
BLAKE2b-256 975b4d6ffe591b39f0b3650fcffbb7168df226ef8caf2b11aa06daa007b9d4d2

See more details on using hashes here.

File details

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

File metadata

  • Download URL: cordless-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 11.3 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f4faa410065f33feea1d1a6b66ab49b110e122b3ee1e38829b276a17b9ebeb32
MD5 bcb5f5b73c83bd6de08afdd9abf43489
BLAKE2b-256 c34c49f5654366eb1c63824f99e6e9d35c93a82abf8707475d9fd99e50c2f056

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