npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tlj-blocks/feedback-cdk

v0.1.0

Published

AWS CDK construct that receives feedback via a Lambda Function URL and emails it through an SNS topic

Readme

@tlj-blocks/feedback-cdk

An AWS CDK construct that adds a feedback receiver to any CDK stack. It creates a Lambda behind a public Function URL that accepts { type, title, description } posts and publishes them to an SNS topic. The topic is one the construct creates and emails to an address you configure, or one you already have.

The Lambda handler is bundled into this package, so you do not need esbuild, Docker, or any bundling setup in your own app. Instantiate the construct and deploy.

Pair it with @tlj-blocks/feedback-client on the frontend, or post to the endpoint yourself. See HTTP contract.

Install

npm install @tlj-blocks/feedback-cdk

aws-cdk-lib and constructs are peer dependencies. Your CDK app already has them, but if not:

npm install aws-cdk-lib constructs

Requires aws-cdk-lib 2.160.0 or later, for lambda.Runtime.NODEJS_22_X, and constructs 10.x.

Quick start

Add the construct to a stack. The only required prop is feedbackEmail (or topic, below).

// lib/my-stack.ts
import { Stack, StackProps, CfnOutput } from "aws-cdk-lib";
import { Construct } from "constructs";
import { Feedback } from "@tlj-blocks/feedback-cdk";

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const feedback = new Feedback(this, "Feedback", {
      feedbackEmail: "[email protected]",
      appName: "MyApp",
      // Lock CORS down to your real origins in production.
      allowedOrigins: ["https://app.example.com", "http://localhost:5173"],
    });

    // Expose the endpoint so you can hand it to the frontend.
    new CfnOutput(this, "FeedbackUrl", { value: feedback.functionUrl.url });
  }
}
// bin/app.ts
import { App } from "aws-cdk-lib";
import { MyStack } from "../lib/my-stack";

const app = new App();
new MyStack(app, "MyStack");

Deploy and confirm

cdk deploy

The stack output FeedbackUrl prints the endpoint URL.

AWS then sends a subscription confirmation email to feedbackEmail. Open it and click "Confirm subscription". SNS delivers nothing until you do.

Submit a test from your frontend, or with the curl below. The feedback should arrive by email.

Bring your own topic

If your app already has a notification topic, or you want to own the topic's subscribers and lifecycle, pass the topic instead of an address:

import { Topic } from "aws-cdk-lib/aws-sns";

const topic = new Topic(this, "Notifications");
const feedback = new Feedback(this, "Feedback", { topic, appName: "MyApp" });

The construct then creates no topic and no subscription. It only publishes to yours. If you give feedbackEmail as well, the construct subscribes that address to your topic too. One of the two props is required.

Wire up the frontend

Give the FeedbackUrl to the @tlj-blocks/feedback-client:

import { submitFeedback } from "@tlj-blocks/feedback-client";

await submitFeedback({
  endpoint: feedbackUrl, // the CfnOutput value, via a build-time env var
  feedback: {
    type: "bug", // "bug" or "feature"
    title: "Save button does nothing",
    description: "Clicking Save on the settings page has no effect.",
  },
});

HTTP contract

You do not need the client. Any HTTP caller works.

Post to the Function URL with Content-Type: application/json:

{
  "type": "bug",
  "title": "Save button does nothing",
  "description": "Clicking Save on the settings page has no effect.",
  "email": "[email protected]",
  "system": {
    "App version": "1.4.0",
    "OS": "macos (aarch64)",
    "Settings": "{\n  \"theme\": \"dark\"\n}"
  }
}

type is "bug" or "feature" and is required. title and description are required non-empty strings. email is optional, and the notification includes it when present.

system is optional. It holds whatever the app knows about the reporter's environment, as a flat object of string values. The email prints it after the description under a --- System --- line, one key: value per entry in the order given. A value containing line breaks is printed indented under its key, so a block of JSON reads as JSON. A value that isn't a string is a 400.

The responses are:

| Status | Body | When | | ------ | ---------------------------------------------------- | -------------------------------- | | 200 | { "success": true } | Published to SNS | | 400 | { "error": "ValidationError", "message": "..." } | Missing or invalid body | | 500 | { "error": "ConfigError" \| "InternalError", ... } | Misconfig or SNS publish failure |

curl -X POST "$FEEDBACK_URL" \
  -H "Content-Type: application/json" \
  -d '{"type":"feature","title":"Dark mode","description":"Please add a dark theme."}'

Props

| Prop | Type | Default | Notes | | ---------------- | ---------------- | ------------- | ----------------------------------------------------------- | | feedbackEmail | string | none | Who receives the feedback emails. Required without topic. | | topic | sns.ITopic | a new topic | Publish to this topic instead of creating one. | | appName | string | "App" | Topic display name and email subject prefix. | | topicName | string | generated | Names the SNS topic the construct creates. | | allowedOrigins | string[] | ["*"] | CORS origins allowed to call the Function URL. | | runtime | lambda.Runtime | NODEJS_22_X | Lambda runtime for the receiver. |

What the construct exposes

The underlying resources are public so you can extend them.

functionUrl is the lambda.FunctionUrl. Read .url for the endpoint.

topic is the sns.ITopic, either the one you passed in or the one the construct made. Add more subscribers to it, such as SMS or extra email addresses.

handler is the lambda.Function. Use it to add alarms, metrics, environment variables, or log retention.

To notify a second address and alarm on errors:

import { Subscription, SubscriptionProtocol } from "aws-cdk-lib/aws-sns";

const feedback = new Feedback(this, "Feedback", {
  feedbackEmail: "[email protected]",
});

// Copy another mailbox.
new Subscription(this, "Cc", {
  topic: feedback.topic,
  protocol: SubscriptionProtocol.EMAIL,
  endpoint: "[email protected]",
});

// Alarm if the receiver starts erroring.
feedback.handler
  .metricErrors()
  .createAlarm(this, "FeedbackErrors", { threshold: 1, evaluationPeriods: 1 });

Resources created

An AWS::SNS::Topic (unless you passed one in), an AWS::SNS::Subscription of protocol email (when feedbackEmail is given), an AWS::Lambda::Function with an execution role that can call sns:Publish on that topic and nothing else, and an AWS::Lambda::Url.

Authentication

The Function URL is public. It uses authType: NONE, so anyone who has the URL can post feedback, which suits anonymous in-app feedback. The handler reads the optional email field from the body but does not verify who sent it.

If you need authenticated submissions, put your own authorizer or API Gateway in front of the handler, restrict allowedOrigins, or extend the construct.

Troubleshooting

If no emails arrive, the SNS subscription is almost certainly unconfirmed. Look in the inbox and the spam folder for the confirmation from AWS and click the link.

A CORS error in the browser means the page's origin is missing from allowedOrigins.

A 500 ConfigError means the Lambda has no FEEDBACK_TOPIC_ARN. The construct sets that variable, so this points to someone editing the function's configuration by hand.