
Security News
OpenCode User Hit with Hostile Backlash After Reporting Free AI Loophole
An OpenCode user faced harassment after reporting a free AI usage loophole, drawing a bitter backlash in a GitHub thread that has since been deleted.
convex-lettermint
Advanced tools
This component integrates the Lettermint transactional email service with Convex.
This project is an independent, unofficial Convex component for Lettermint. It is not affiliated with, endorsed by, sponsored by, or maintained by Lettermint.
Lettermint and related names, logos, product names, and trademarks are the property of Lettermint or their respective owners. All rights are reserved by their respective owners. Any use of Lettermint marks in this project is for descriptive and referential purposes only.
Features:
/send/batch endpoint.Idempotency-Key values.See example for a demo of how to incorporate this component into your application.
npm install convex-lettermint
Create a Lettermint project and create a Project API
Add the component to your Convex app via convex/convex.config.ts:
import { defineApp } from "convex/server";
import lettermint from "convex-lettermint/convex.config.js";
const app = defineApp();
app.use(lettermint);
export default app;
Use it from a Convex function:
import { components } from "./_generated/api";
import { Lettermint } from "convex-lettermint";
import { internalMutation } from "./_generated/server";
export const lettermint = new Lettermint(components.lettermint, {});
export const sendTestEmail = internalMutation({
handler: async (ctx) => {
await lettermint.sendEmail(ctx, {
from: "Me <test@mydomain.com>",
to: "ok@testing.lettermint.co",
subject: "Hi there",
html: "This is a test email",
});
},
});
testMode is enabled by default. In test mode, recipients must use the
Lettermint test domain @testing.lettermint.co. Examples include:
ok@testing.lettermint.cosoftbounce@testing.lettermint.cohardbounce@testing.lettermint.cospamcomplaint@testing.lettermint.codsn@testing.lettermint.coSet testMode: false in LettermintOptions before sending to real recipients.
The component can receive Lettermint delivery, bounce, complaint, open, and click events.
Mount an HTTP endpoint in convex/http.ts:
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
import { lettermint } from "./sendEmails";
const http = httpRouter();
http.route({
path: "/lettermint-webhook",
method: "POST",
handler: httpAction(async (ctx, req) => {
return await lettermint.handleLettermintEventWebhook(ctx, req);
}),
});
export default http;
Create a webhook in the Lettermint dashboard for that URL and subscribe to the
message events you need. Copy the webhook secret and set it to
LETTERMINT_WEBHOOK_SECRET in your Convex deployment environment.
Lettermint signs webhooks with X-Lettermint-Signature. The component verifies
the HMAC-SHA256 signature and rejects invalid or stale webhook requests.
Register a mutation to run when email events arrive:
import { components, internal } from "./_generated/api";
import { internalMutation } from "./_generated/server";
import { Lettermint, vOnEmailEventArgs } from "convex-lettermint";
export const lettermint = new Lettermint(components.lettermint, {
onEmailEvent: internal.example.handleEmailEvent,
});
export const handleEmailEvent = internalMutation({
args: vOnEmailEventArgs,
handler: async (ctx, args) => {
// args provides { id: EmailId; event: EmailEvent; }
if (args.event.event === "message.delivered") {
// Handle delivery.
}
},
});
Supported outbound Lettermint events include message.created, message.sent,
message.delivered, message.hard_bounced, message.soft_bounced,
message.spam_complaint, message.failed, message.suppressed,
message.policy_rejected, message.unsubscribed, message.opened, and
message.clicked. webhook.test is acknowledged and ignored.
The LettermintOptions constructor argument customizes component behavior:
projectToken: Provide the Lettermint Project API token instead of reading
LETTERMINT_PROJECT_TOKEN.webhookSecret: Provide the webhook secret instead of reading
LETTERMINT_WEBHOOK_SECRET.testMode: Only allow delivery to Lettermint test addresses. Defaults to
true.initialBackoffMs: Initial retry backoff for failed API calls. Defaults to 30
seconds.retryAttempts: Maximum retry attempts for failed API calls. Defaults to 5.onEmailEvent: Mutation callback for Lettermint webhook events.sendEmail supports the batch-safe Lettermint fields:
fromtoccbccsubjecthtmltextreplyToheadersroutetagmetadatasettings.trackOpenssettings.trackClicksExample:
await lettermint.sendEmail(ctx, {
from: "Notifications <notifications@mydomain.com>",
to: "ok@testing.lettermint.co",
subject: "Welcome!",
html: "<p>Welcome to our app.</p>",
route: "transactional",
tag: "welcome-email",
metadata: {
user_id: "user_123",
},
settings: {
trackOpens: false,
trackClicks: true,
},
});
Templates are not supported by the Lettermint Sending API schema used by this
component. Generate dynamic HTML yourself or use React Email before calling
sendEmail.
sendEmail returns a branded EmailId.
Use it to check status or cancel queued emails:
const emailId = await lettermint.sendEmail(ctx, {
from: "Me <test@mydomain.com>",
to: "ok@testing.lettermint.co",
subject: "Hi",
text: "Hello",
});
const emailStatus = await lettermint.status(ctx, emailId);
if (emailStatus) {
console.log(emailStatus.status);
console.log(emailStatus.bounced);
console.log(emailStatus.failed);
console.log(emailStatus.complained);
console.log(emailStatus.opened);
console.log(emailStatus.clicked);
console.log(emailStatus.errorMessage);
}
await lettermint.cancelEmail(ctx, emailId);
If an email has already been sent to Lettermint, it cannot be cancelled.
This component retains finalized emails and delivery events. You can clear old
data with cleanupOldEmails and cleanupAbandonedEmails, either from the
Convex dashboard or a cron job:
import { cronJobs } from "convex/server";
import { components, internal } from "./_generated/api.js";
import { internalMutation } from "./_generated/server.js";
const crons = cronJobs();
crons.interval(
"Remove old emails from the Lettermint component",
{ hours: 1 },
internal.crons.cleanupLettermint,
);
const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;
export const cleanupLettermint = internalMutation({
args: {},
handler: async (ctx) => {
await ctx.scheduler.runAfter(0, components.lettermint.lib.cleanupOldEmails, {
olderThan: ONE_WEEK_MS,
});
await ctx.scheduler.runAfter(
0,
components.lettermint.lib.cleanupAbandonedEmails,
{ olderThan: 4 * ONE_WEEK_MS },
);
},
});
export default crons;
Use React Email to generate HTML in a Convex Node action,
then pass the generated HTML to sendEmail.
"use node";
import { action } from "./_generated/server";
import { render, pretty } from "@react-email/render";
import { Button, Html } from "@react-email/components";
import { components } from "./_generated/api";
import { Lettermint } from "convex-lettermint";
export const lettermint = new Lettermint(components.lettermint, {
testMode: false,
});
export const sendEmail = action({
args: {},
handler: async (ctx) => {
const html = await pretty(
await render(
<Html>
<Button href="https://example.com">Click me</Button>
</Html>,
),
);
await lettermint.sendEmail(ctx, {
from: "Me <test@mydomain.com>",
to: "recipient@example.com",
subject: "Hi there",
html,
});
},
});
Attachments are intentionally not part of the durable queue API. Use
sendEmailManually when you need Lettermint features that should be sent
immediately, including attachments or inline images.
import { components } from "./_generated/api";
import { internalAction } from "./_generated/server";
import { Lettermint } from "convex-lettermint";
export const lettermint = new Lettermint(components.lettermint, {});
export const sendManualEmail = internalAction({
args: {},
handler: async (ctx) => {
const from = "Acme <onboarding@example.com>";
const to = ["ok@testing.lettermint.co"];
const subject = "hello world";
const html = "<p>it works!</p>";
await lettermint.sendEmailManually(
ctx,
{ from, to, subject },
async (emailId) => {
const response = await fetch("https://api.lettermint.co/v1/send", {
method: "POST",
headers: {
"x-lettermint-token": process.env.LETTERMINT_PROJECT_TOKEN!,
"Content-Type": "application/json",
"Idempotency-Key": emailId,
},
body: JSON.stringify({
from,
to,
subject,
html,
attachments: [
{
filename: "invoice.pdf",
content: "base64-encoded-content",
},
],
}),
});
if (!response.ok) {
throw new Error(`[Email] Failed to send: ${await response.text()}`);
}
const data = (await response.json()) as { message_id: string };
return data.message_id;
},
);
},
});
FAQs
A Lettermint component for Convex.
The npm package convex-lettermint receives a total of 45 weekly downloads. As such, convex-lettermint popularity was classified as not popular.
We found that convex-lettermint demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
An OpenCode user faced harassment after reporting a free AI usage loophole, drawing a bitter backlash in a GitHub thread that has since been deleted.

Threat Research
/Security News
319 browser extensions installed by 13.4 million users hijack searches, capture queries, and collect referral payments from Yahoo and Bing.

Threat Research
Malicious Firefox extensions mimic Phantom, Jupiter, and Bitget wallets to steal recovery phrases and private keys, with one fetching its payload remotely.