Skip to content

Commit 2a1da12

Browse files
authored
Merge pull request #37 from StefanWin/main
allow simple labels in test email addresses
2 parents 6e5f6f9 + 6ee605f commit 2a1da12

File tree

4 files changed

+55
-8
lines changed

4 files changed

+55
-8
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ Then, calling `sendTestEmail` from anywhere in your app will send this test emai
6767
If you want to send emails to real addresses, you need to disable `testMode`.
6868
You can do this in `ResendOptions`, [as detailed below](#resend-component-options-and-going-into-production).
6969

70+
A note on test email addresses:
71+
[Resend allows the use of labels](https://resend.com/docs/dashboard/emails/send-test-emails#using-labels-effectively) for test emails.
72+
For simplicity, this component only allows labels matching `[a-zA-Z0-9_-]*`, e.g. `[email protected]`.
73+
7074
## Advanced Usage
7175

7276
### Setting up a Resend webhook

src/component/lib.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { isDeepEqual } from "remeda";
2424
import schema from "./schema.js";
2525
import { omit } from "convex-helpers";
2626
import { parse } from "convex-helpers/validators";
27-
import { assertExhaustive, attemptToParse, iife } from "./utils.js";
27+
import { assertExhaustive, attemptToParse, iife, isValidResendTestEmail } from "./utils.js";
2828

2929
// Move some of these to options? TODO
3030
const SEGMENT_MS = 125;
@@ -37,12 +37,6 @@ const FINALIZED_EMAIL_RETENTION_MS = 1000 * 60 * 60 * 24 * 7; // 7 days
3737
const FINALIZED_EPOCH = Number.MAX_SAFE_INTEGER;
3838
const ABANDONED_EMAIL_RETENTION_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
3939

40-
const RESEND_TEST_EMAILS = new Set([
41-
42-
43-
44-
]);
45-
4640
const PERMANENT_ERROR_CODES = new Set([
4741
400, 401 /* 402 not included - unclear spec */, 403, 404, 405, 406, 407, 408,
4842
/* 409 not included - conflict may work on retry */
@@ -100,7 +94,7 @@ export const sendEmail = mutation({
10094
returns: v.id("emails"),
10195
handler: async (ctx, args) => {
10296
// We only allow test emails in test mode.
103-
if (args.options.testMode && !RESEND_TEST_EMAILS.has(args.to)) {
97+
if (args.options.testMode && !isValidResendTestEmail(args.to)) {
10498
throw new Error(
10599
`Test mode is enabled, but email address is not a valid resend test address. Did you want to set testMode: false in your ResendOptions?`
106100
);

src/component/utils.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { describe, it, expect } from "vitest";
2+
import { isValidResendTestEmail } from "./utils.js";
3+
4+
describe("isValidResendTestEmail", () => {
5+
6+
it("allows valid test emails without labels", () => {
7+
expect(isValidResendTestEmail("[email protected]")).toBe(true);
8+
expect(isValidResendTestEmail("[email protected]")).toBe(true);
9+
expect(isValidResendTestEmail("[email protected]")).toBe(true);
10+
});
11+
12+
it("allows valid test emails with labels", () => {
13+
expect(isValidResendTestEmail("[email protected]")).toBe(true);
14+
expect(isValidResendTestEmail("[email protected]")).toBe(true);
15+
expect(isValidResendTestEmail("[email protected]")).toBe(true);
16+
expect(isValidResendTestEmail("[email protected]")).toBe(true);
17+
expect(isValidResendTestEmail("[email protected]")).toBe(true);
18+
expect(isValidResendTestEmail("[email protected]")).toBe(true);
19+
});
20+
21+
it("rejects invalid test emails with or without labels", () => {
22+
expect(isValidResendTestEmail("[email protected]")).toBe(false);
23+
expect(isValidResendTestEmail("[email protected]")).toBe(false);
24+
expect(isValidResendTestEmail("[email protected]")).toBe(false);
25+
});
26+
27+
it("rejects test emails with disallowed special characters in the label", () => {
28+
expect(isValidResendTestEmail("[email protected]")).toBe(false);
29+
expect(isValidResendTestEmail("bounced+user/[email protected]")).toBe(false);
30+
expect(isValidResendTestEmail("[email protected]")).toBe(false);
31+
});
32+
33+
});

src/component/utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,19 @@ export function attemptToParse<T extends Validator<any, any, any>>(
2727
};
2828
}
2929
}
30+
31+
/**
32+
* This is one is intentionally kept simple and only allows - and _ as special characters
33+
* since Resend does not specify what pattern a label must follow, the documentation
34+
* only says "any string": https://resend.com/docs/dashboard/emails/send-test-emails#using-labels-effectively
35+
* and a full RFC-5322 compliant regex would be way too long.
36+
*/
37+
const RESEND_TEST_EMAIL_REGEX = /^(delivered|bounced|complained)(\+[a-zA-Z0-9_-]*)?@resend\.dev$/;
38+
39+
/**
40+
* Check if the given e-mail address is a valid test e-mail for Resend.
41+
* @param email
42+
*/
43+
export function isValidResendTestEmail(email: string): boolean {
44+
return RESEND_TEST_EMAIL_REGEX.test(email);
45+
}

0 commit comments

Comments
 (0)