create-event-handlers
Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9. RabbitMQ only (no Azure Service Bus). Creates publishers with DomainEvent (tenantId preferred), consumers with handlers, naming {domain}.{entity}.{action}, required fields (id, type, version, timestamp, tenantId, source, data). Use when adding event-driven communication, async workflows, or integrating via events.
Best use case
create-event-handlers is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9. RabbitMQ only (no Azure Service Bus). Creates publishers with DomainEvent (tenantId preferred), consumers with handlers, naming {domain}.{entity}.{action}, required fields (id, type, version, timestamp, tenantId, source, data). Use when adding event-driven communication, async workflows, or integrating via events.
Teams using create-event-handlers should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/create-event-handlers/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How create-event-handlers Compares
| Feature / Agent | create-event-handlers | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9. RabbitMQ only (no Azure Service Bus). Creates publishers with DomainEvent (tenantId preferred), consumers with handlers, naming {domain}.{entity}.{action}, required fields (id, type, version, timestamp, tenantId, source, data). Use when adding event-driven communication, async workflows, or integrating via events.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Create Event Handlers
Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9.
## Event Naming Convention
Reference: ModuleImplementationGuide.md Section 9.1
Format: `{domain}.{entity}.{action}`
✅ **Correct:**
- `user.created`
- `auth.login.success`
- `notification.email.sent`
- `secret.rotated`
❌ **Wrong:**
- `userCreated`
- `loginSuccess`
- `emailSent`
**Standard Actions:**
- `created`, `updated`, `deleted`
- `started`, `completed`, `failed`
- `sent`, `received`, `expired`, `rotated`
## Event Structure
Reference: ModuleImplementationGuide.md Section 9.2
```typescript
interface DomainEvent<T = unknown> {
// Identity
id: string; // Unique event ID (UUID)
type: string; // Event type (domain.entity.action)
// Metadata
timestamp: string; // ISO 8601
version: string; // Event schema version
source: string; // Module that emitted
correlationId?: string; // Request correlation
// Context
tenantId?: string; // Tenant context (PREFERRED; use for new modules)
organizationId?: string; // DEPRECATED for new modules; prefer tenantId
userId?: string; // Actor
// Payload
data: T; // Event-specific data
}
```
## Event Publisher
Reference: containers/auth/src/events/publishers/AuthEventPublisher.ts
### src/events/publishers/[Module]EventPublisher.ts
```typescript
import { randomUUID } from 'crypto';
import { EventPublisher, getChannel, closeConnection } from '@coder/shared';
import { log } from '../../utils/logger';
import { getConfig } from '../../config';
let publisher: EventPublisher | null = null;
export async function initializeEventPublisher(): Promise<void> {
if (publisher) return;
const config = getConfig();
if (!config.rabbitmq?.url) {
log.warn('RabbitMQ URL not configured, events will not be published');
return;
}
try {
await getChannel();
publisher = new EventPublisher(config.rabbitmq.exchange || 'coder_events');
log.info('Event publisher initialized', { exchange: config.rabbitmq.exchange });
} catch (error: any) {
log.error('Failed to initialize event publisher', error);
}
}
export async function closeEventPublisher(): Promise<void> {
try {
await closeConnection();
publisher = null;
} catch (error: any) {
log.error('Error closing event publisher', error);
}
}
function getPublisher(): EventPublisher | null {
if (!publisher) {
const config = getConfig();
publisher = new EventPublisher(config.rabbitmq.exchange || 'coder_events');
}
return publisher;
}
export function createBaseEvent(
type: string,
userId?: string,
tenantId?: string,
correlationId?: string,
data?: any
) {
return {
id: randomUUID(),
type,
timestamp: new Date().toISOString(),
version: '1.0',
source: '[module-name]',
correlationId,
tenantId,
userId,
data: data || {},
};
}
export async function publishEvent(event: any, routingKey?: string): Promise<void> {
const pub = getPublisher();
if (!pub) {
log.warn('Event publisher not initialized, skipping event', { type: event.type });
return;
}
try {
await pub.publish(routingKey || event.type, event);
log.debug('Event published', { type: event.type, id: event.id });
} catch (error: any) {
log.error('Failed to publish event', error, { type: event.type });
}
}
```
### Usage in Services
```typescript
import { publishEvent, createBaseEvent } from '../events/publishers/ModuleEventPublisher';
// Publish event (tenantId for tenant context)
const event = createBaseEvent(
'resource.created',
userId,
tenantId,
correlationId,
{
resourceId: resource.id,
name: resource.name,
}
);
await publishEvent(event);
```
## Event Consumer
Reference: ModuleImplementationGuide.md Section 9.4
### src/events/consumers/[Resource]Consumer.ts
```typescript
import { EventConsumer } from '@coder/shared';
import { log } from '../../utils/logger';
import { getConfig } from '../../config';
let consumer: EventConsumer | null = null;
export async function initializeEventConsumer(): Promise<void> {
if (consumer) return;
const config = getConfig();
if (!config.rabbitmq?.url) {
log.warn('RabbitMQ URL not configured, events will not be consumed');
return;
}
try {
consumer = new EventConsumer({
queue: config.rabbitmq.queue || '[module-name]_service',
exchange: config.rabbitmq.exchange || 'coder_events',
bindings: config.rabbitmq.bindings || [],
});
// Register handlers
consumer.on('other.resource.created', handleResourceCreated);
consumer.on('other.resource.updated', handleResourceUpdated);
await consumer.start();
log.info('Event consumer initialized', { queue: config.rabbitmq.queue });
} catch (error: any) {
log.error('Failed to initialize event consumer', error);
}
}
async function handleResourceCreated(event: any): Promise<void> {
log.info('Resource created event received', { resourceId: event.data.resourceId });
// Handle the event
}
async function handleResourceUpdated(event: any): Promise<void> {
log.info('Resource updated event received', { resourceId: event.data.resourceId });
// Handle the event
}
export async function closeEventConsumer(): Promise<void> {
try {
if (consumer) {
await consumer.stop();
consumer = null;
}
} catch (error: any) {
log.error('Error closing event consumer', error);
}
}
```
## Event Documentation
Reference: ModuleImplementationGuide.md Section 9.5
### logs-events.md (if events are logged)
Create in module root if module publishes events that get logged:
```markdown
# [Module Name] - Logs Events
## Published Events
### {domain}.{entity}.{action}
**Description**: When this event is triggered.
**Triggered When**:
- Condition 1
- Condition 2
**Event Type**: `{domain}.{entity}.{action}`
**Event Schema**:
\`\`\`json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["id", "type", "timestamp", "version", "source", "data"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"type": { "type": "string" },
"timestamp": { "type": "string", "format": "date-time" },
"version": { "type": "string" },
"source": { "type": "string" },
"tenantId": { "type": "string", "format": "uuid" },
"userId": { "type": "string", "format": "uuid" },
"data": {
"type": "object",
"properties": {
"resourceId": { "type": "string" }
}
}
}
}
\`\`\`
```
### notifications-events.md (if events trigger notifications)
Create in module root if module publishes events that trigger notifications.
## Configuration
Add to config/default.yaml:
```yaml
rabbitmq:
url: ${RABBITMQ_URL}
exchange: coder_events
queue: [module-name]_service
bindings:
- "other.resource.created"
- "other.resource.updated"
```
## Checklist
- [ ] Event publisher created following pattern
- [ ] Event consumer created (if consuming events)
- [ ] Events follow naming convention: {domain}.{entity}.{action}
- [ ] Events include all required fields (id, type, version, timestamp, source, data)
- [ ] Events include tenantId when applicable (RabbitMQ only; no Azure Service Bus)
- [ ] logs-events.md created (if events are logged)
- [ ] notifications-events.md created (if events trigger notifications)
- [ ] RabbitMQ config added to default.yamlRelated Skills
kitt-create-slash-commands
Expert guidance for creating slash commands. Use when working with slash commands, creating custom commands, understanding command structure, or learning YAML configuration.
kitt-create-plans
Create hierarchical project plans optimized for solo agentic development. Use when planning projects, phases, or tasks that the AI agent will execute. Produces agent-executable plans with verification criteria, not enterprise documentation. Handles briefs, roadmaps, phase plans, and context handoffs.
event-driven
Event-driven architecture patterns including message queues, pub/sub, event sourcing, CQRS, and sagas. Use when implementing async messaging, distributed transactions, event stores, command query separation, domain events, integration events, data streaming, choreography, orchestration, or integrating with RabbitMQ, Kafka, Apache Pulsar, AWS SQS, AWS SNS, NATS, event buses, or message brokers.
create-workflow
Create Jazz workflow automation files (WORKFLOW.md). Use this for scheduling Jazz agents to run recurring tasks. For OS-level scripts/commands, use create-system-routine.
create-rule
Create persistent AI agent rules and instructions. Use when you want to create a rule, add coding standards, set up project conventions, configure file-specific patterns, or create AGENTS.md/GEMINI.md rule files across Cursor, Gemini CLI, or Codex.
create-prompt
Expert prompt engineering for creating effective prompts for Claude, GPT, and other LLMs. Use when writing system prompts, user prompts, few-shot examples, or optimizing existing prompts for better performance.
create-expert-skill
Create production-ready skills from expert knowledge. Extracts domain expertise and system ontologies, uses scripts for deterministic work, loads knowledge progressively. Use when building skills that must work reliably in production.
create-custom-prompt
Prompt for creating custom prompt files
create-assistant
Create and configure Vapi voice AI assistants with models, voices, transcribers, tools, hooks, and advanced settings. Use when building voice agents, phone bots, customer support assistants, or any conversational AI that handles phone or web calls.
create-agents-md
Create or rewrite AGENTS.md files for Open Mercato packages and modules. Use this skill when adding a new package, creating a new module, or when an existing AGENTS.md needs to be created or refactored. Ensures prescriptive tone, MUST rules, checklists, and consistent structure across all agent guidelines.
create-agent-with-sanity-context
Build AI agents with structured access to Sanity content via Context MCP. Covers Studio setup, agent implementation, and advanced patterns like client-side tools and custom rendering.
awesome-copilot-root-typespec-create-agent
Generate a complete TypeSpec declarative agent with instructions, capabilities, and conversation starters for Microsoft 365 Copilot Use when: the task directly matches typespec create agent responsibilities within plugin awesome-copilot-root. Do not use when: a more specific framework or task-focused skill is clearly a better match.