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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@hamzasaleemorg/convex-inbound

v0.1.4

Published

A Convex component for inbound.new - send, receive, and reply to emails with durable execution, batching, and full tracking.

Downloads

232

Readme

Convex Inbound Component

npm version Convex

A drop-in Convex Component that adds full-stack email capabilities to your Convex app, powered by inbound.new.

Stop building email infra from scratch. This component gives you a production-ready email backend with one line of config.

Why use this Component?

Instead of just calling an API, this component runs inside your Convex backend:

  • ⚡️ Durable Sending: Emails are queued and sent via background workers (Workpool). If the API fails, it retries automatically.
  • 📩 Threaded Inbox: Inbound emails are stored directly in your database table with proper threading.
  • 🛡️ Idempotency: Built-in protection against duplicate sends, even during heavy retries.
  • 🚦 Rate Limiting: Automatically handles API limits so you never get 429s.
  • 🔒 Type-Safe: Full TypeScript support for all events and methods.

Installation

npm install @hamzasaleemorg/convex-inbound

Setup

1. Register Component

Add it to your convex/convex.config.ts:

import { defineApp } from "convex/server";
import inbound from "@hamzasaleemorg/convex-inbound/convex.config";

const app = defineApp();
app.use(inbound); // Installs the component

export default app;

2. Configure Environment

# Get your API key from inbound.new
npx convex env set INBOUND_API_KEY sk_live_...

3. Initialize Client

Create an instance in your backend (e.g., convex/myFunctions.ts):

import { components } from "./_generated/api";
import { Inbound } from "@hamzasaleemorg/convex-inbound";

// Initialize with the installed component
const inbound = new Inbound(components.inbound, { testMode: false });

Usage

📤 Sending Emails (Durable)

This doesn't just call an API—it schedules a durable job. It will succeed even if your function times out or the external API blips.

export const sendWelcome = mutation({
  handler: async (ctx) => {
    await inbound.send(ctx, {
      from: "[email protected]",
      to: "[email protected]",
      subject: "Welcome to the Platform",
      html: "<p>We are glad to have you!</p>",
    });
  },
});

📩 Receiving Emails (Webhook)

Receive emails directly into your Convex database.

  1. Expose the Webhook:

    // convex/http.ts
    import { httpRouter } from "convex/server";
    import { httpAction } from "./_generated/server";
    import { Inbound } from "@hamzasaleemorg/convex-inbound";
    import { components } from "./_generated/api";
    
    const http = httpRouter();
    const inbound = new Inbound(components.inbound);
    
    http.route({
      path: "/inbound-webhook",
      method: "POST",
      handler: httpAction(async (ctx, request) => {
        return await inbound.handleInboundWebhook(ctx, request);
      }),
    });
    
    export default http;
  2. Point inbound.new to it:

    • URL: https://<your-convex-deployment>.convex.site/inbound-webhook
    • (Optional) Set INBOUND_WEBHOOK_SECRET env var and add X-Webhook-Secret header in dashboard for security.

💬 Replying (Threaded)

Reply to an inbound email while maintaining the correct conversation thread (In-Reply-To, References).

export const replyToUser = mutation({
  args: { emailId: v.id("inbound_emails") },
  handler: async (ctx, args) => {
    await inbound.reply(ctx, {
      inboundEmailId: args.emailId,
      text: "Thanks for your report! We're looking into it.",
    });
  },
});

🔍 Status & Tracking

Check if an email was delivered, bounced, or opened.

const status = await inbound.status(ctx, emailId);

if (status.failed) {
  console.error("Email failed:", status.errorMessage);
} else if (status.opened) {
  console.log("User read the email!");
}

API Reference

Inbound Class

const inbound = new Inbound(component, options?);

Options:

  • apiKey (optional): Override INBOUND_API_KEY.
  • webhookSecret (optional): Override INBOUND_WEBHOOK_SECRET.
  • testMode (default: true): If true, only allows sending to @inbnd.dev and @example.com.

Methods

| Method | Returns | Description | | :--- | :--- | :--- | | send(ctx, options) | Promise<string> | Queues a durable email send. Returns Email ID. | | sendBatch(ctx, emails[]) | Promise<string[]> | Queues multiple emails efficiently. | | reply(ctx, options) | Promise<string> | Replis to an inbound email with threading. | | status(ctx, emailId) | Promise<Object> | Gets delivery status (sent, bounced, opened, etc). | | cancelEmail(ctx, emailId) | Promise<void> | Cancels a queued email if not yet sent. | | listInboundEmails(ctx) | Promise<Email[]> | Returns received emails. | | listOutboundEmails(ctx) | Promise<Email[]> | Returns sent emails. |


Data Management

Automatic Cleanup via Cron

Add this to convex/crons.ts to keep your tables clean:

import { cronJobs } from "convex/server";
import { components } from "./_generated/api";

const crons = cronJobs();

// Clean up old emails every hour
crons.interval(
  "cleanup-emails",
  { hours: 1 },
  components.inbound.lib.cleanupOldEmails,
  { olderThan: 7 * 24 * 60 * 60 * 1000 } // 7 days
);

export default crons;