Skip to content

Commit 82bb7f6

Browse files
Update cloudflare-email-routing extension (#24857)
* Update cloudflare-email-routing extension - Enhance alias management features: add editable alias defaults, allow alias slug editing, and improve alias creation process. Update changelog and adjust package configurations accordingly. - first commit * Update cloudflare-email-routing extension - Refactor CreateAlias and ListAliases components for cleaner code and improved readability. Simplify the alias input field in CreateAlias and streamline accessory creation in AliasItem. - Refactor alias components for improved user experience and functionality. Update CreateAlias to clarify alias input placeholder, enhance ListAliases with edit confirmation for non-managed aliases, and ensure accurate display of creation dates. Adjust AliasRule type to include isManaged flag and make createdAt optional. - Improve alias validation in CreateAlias component by ensuring only one \'@\' symbol is present and refining the extraction of local part and domain. This enhances error handling for invalid alias formats. * Update CreateAlias component to return updated email after rule modification. Refactor updateRule function to return AliasRule instead of void, ensuring accurate email handling in clipboard operations and toast messages. * Refactor AliasItem and getUnusedRules for improved type safety and functionality. Update accessory creation in AliasItem to explicitly define types and enhance date sorting in getUnusedRules to handle optional createdAt values. * Update CHANGELOG.md --------- Co-authored-by: raycastbot <bot@raycast.com>
1 parent e03dfbc commit 82bb7f6

File tree

12 files changed

+367
-181
lines changed

12 files changed

+367
-181
lines changed

extensions/cloudflare-email-routing/CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Cloudflare Email Routing Changelog
22

3+
## Editable Alias Defaults - 2026-02-06
4+
5+
- Add configurable alias preface and default label preferences
6+
- Allow editing the alias slug when creating new aliases
7+
- Show all Cloudflare Email Routing aliases for the zone
8+
- Copies the new alias email address to the clipboard when created
9+
310
## [Update README + Use SDK for List Aliases] - 2025-12-01
411

512
- Update README to clarify which permissions are required
@@ -13,4 +20,4 @@
1320
- **List & Filter Aliases:** View all your existing aliases. Search and filter them by email address, description, or tags.
1421
- **Manage Aliases:** Edit the label and description of your aliases or delete them directly from Raycast.
1522
- **View Alias Details:** See detailed information for each alias, including its creation date and forwarding address.
16-
- **Colored Tags:** Tags are assigned a unique, consistent color for easier visual identification.
23+
- **Colored Tags:** Tags are assigned a unique, consistent color for easier visual identification.

extensions/cloudflare-email-routing/package-lock.json

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/cloudflare-email-routing/package.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"icon": "extension-icon.png",
77
"author": "erayack",
88
"contributors": [
9-
"xmok"
9+
"xmok",
10+
"parterburn"
1011
],
1112
"categories": [
1213
"Productivity",
@@ -75,6 +76,22 @@
7576
"description": "Your real email address where aliases will forward to",
7677
"placeholder": "your-real-email@example.com"
7778
},
79+
{
80+
"name": "aliasPreface",
81+
"type": "textfield",
82+
"required": false,
83+
"title": "Alias Preface (Optional)",
84+
"description": "Prefix new aliases with a preface, \"-\" separator is automatically added (e.g. preface-slug)",
85+
"placeholder": "preface"
86+
},
87+
{
88+
"name": "defaultLabel",
89+
"type": "textfield",
90+
"required": false,
91+
"title": "Default Alias Label",
92+
"description": "Default label applied to newly created aliases",
93+
"placeholder": "work"
94+
},
7895
{
7996
"name": "preAllocatePool",
8097
"type": "checkbox",

extensions/cloudflare-email-routing/src/commands/create-alias.tsx

Lines changed: 106 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,23 @@ import { useState } from "react";
22
import { Form, ActionPanel, Action, showToast, Toast, popToRoot, Clipboard } from "@raycast/api";
33
import { useForm, useCachedPromise } from "@raycast/utils";
44
import { CreateAliasFormData, CreateAliasProps } from "../types";
5-
import { validateLabel, validateDescription, extractDomainFromEmail } from "../utils";
5+
import {
6+
validateLabel,
7+
validateDescription,
8+
extractDomainFromEmail,
9+
generateRandomSlug,
10+
validateEmail,
11+
} from "../utils";
612
import { getApiConfig } from "../services/api/config";
713
import { getUnusedRules, createRule, updateRule, ensurePoolSize, getAccountDomain } from "../services/cf/rules";
814

915
export default function CreateAlias({ alias }: CreateAliasProps = {}) {
1016
const [isLoading, setIsLoading] = useState(false);
1117
const config = getApiConfig();
18+
const [defaultAliasSlug] = useState(() => {
19+
const slug = generateRandomSlug();
20+
return config.aliasPreface ? `${config.aliasPreface}-${slug}` : slug;
21+
});
1222

1323
// Fetch the correct domain for alias creation
1424
const { data: domain } = useCachedPromise(async () => {
@@ -29,6 +39,51 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
2939
}
3040
});
3141

42+
const normalizeAliasInput = (input: string, aliasDomain: string): string => {
43+
const trimmed = input.trim();
44+
if (!trimmed) {
45+
throw new Error("Alias is required");
46+
}
47+
48+
if (trimmed.includes("@")) {
49+
const atIndex = trimmed.indexOf("@");
50+
const lastAtIndex = trimmed.lastIndexOf("@");
51+
if (atIndex !== lastAtIndex) {
52+
throw new Error("Alias must include only one @ symbol");
53+
}
54+
const localPart = trimmed.slice(0, atIndex);
55+
const inputDomain = trimmed.slice(atIndex + 1);
56+
if (!localPart || !inputDomain) {
57+
throw new Error("Alias must include a valid domain");
58+
}
59+
if (inputDomain.toLowerCase() !== aliasDomain.toLowerCase()) {
60+
throw new Error(`Alias domain must match ${aliasDomain}`);
61+
}
62+
return `${localPart}@${aliasDomain}`;
63+
}
64+
65+
return `${trimmed}@${aliasDomain}`;
66+
};
67+
68+
const validateAlias = (value?: string): string | undefined => {
69+
if (alias) {
70+
return undefined;
71+
}
72+
if (!value) {
73+
return "Alias is required";
74+
}
75+
if (!domain) {
76+
return "Domain not available yet";
77+
}
78+
79+
try {
80+
const normalized = normalizeAliasInput(value, domain as string);
81+
return validateEmail(normalized) ? undefined : "Alias must be a valid email address";
82+
} catch (error) {
83+
return error instanceof Error ? error.message : "Invalid alias";
84+
}
85+
};
86+
3287
const { handleSubmit, itemProps } = useForm<CreateAliasFormData>({
3388
async onSubmit(values) {
3489
setIsLoading(true);
@@ -66,48 +121,52 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
66121
});
67122
} else {
68123
// Create new alias
124+
const normalizedAlias = normalizeAliasInput(values.alias, safeDomain);
125+
if (!validateEmail(normalizedAlias)) {
126+
throw new Error("Alias must be a valid email address");
127+
}
128+
69129
let unusedRules = await getUnusedRules();
130+
let createdEmail = normalizedAlias;
70131

71-
if (unusedRules.length === 0) {
72-
// No unused rules available, create a new one
132+
if (unusedRules.length > 0) {
133+
const ruleToUse = unusedRules[0];
134+
const updatedRule = await updateRule(ruleToUse.id, values.label, values.description, normalizedAlias);
135+
createdEmail = updatedRule.email;
136+
} else {
73137
showToast({
74138
style: Toast.Style.Animated,
75139
title: "Creating New Alias",
76140
message: "Generating new email alias...",
77141
});
78-
await createRule(safeDomain);
142+
const createdRule = await createRule(safeDomain, normalizedAlias, values.label, values.description);
143+
createdEmail = createdRule.email;
79144
unusedRules = await getUnusedRules();
80145
}
81146

82-
if (unusedRules.length > 0) {
83-
const ruleToUse = unusedRules[0];
84-
await updateRule(ruleToUse.id, values.label, values.description);
85-
86-
showToast({
87-
style: Toast.Style.Success,
88-
title: "Alias Created",
89-
message: `Successfully created ${ruleToUse.email}`,
90-
primaryAction: {
91-
title: "Copy Email",
92-
onAction: () => {
93-
Clipboard.copy(ruleToUse.email);
94-
},
147+
await Clipboard.copy(createdEmail);
148+
showToast({
149+
style: Toast.Style.Success,
150+
title: "Alias Created",
151+
message: `Copied ${createdEmail} to clipboard`,
152+
primaryAction: {
153+
title: "Copy Email",
154+
onAction: () => {
155+
Clipboard.copy(createdEmail);
95156
},
96-
});
157+
},
158+
});
97159

98-
// Ensure pool size after using a rule
99-
if (config.preAllocatePool) {
100-
ensurePoolSize(20).catch((poolError) => {
101-
console.error("Pool size maintenance failed:", poolError);
102-
showToast({
103-
style: Toast.Style.Failure,
104-
title: "Pool Maintenance Warning",
105-
message: "Alias created but pool replenishment failed. Next alias creation may be slower.",
106-
});
160+
// Ensure pool size after using a rule
161+
if (config.preAllocatePool) {
162+
ensurePoolSize(20).catch((poolError) => {
163+
console.error("Pool size maintenance failed:", poolError);
164+
showToast({
165+
style: Toast.Style.Failure,
166+
title: "Pool Maintenance Warning",
167+
message: "Alias created but pool replenishment failed. Next alias creation may be slower.",
107168
});
108-
}
109-
} else {
110-
throw new Error("Failed to create or find available alias");
169+
});
111170
}
112171
}
113172

@@ -130,10 +189,12 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
130189
}
131190
},
132191
initialValues: {
133-
label: alias?.name.label || "",
192+
alias: alias?.email || defaultAliasSlug,
193+
label: alias?.name.label || config.defaultLabel || "",
134194
description: alias?.name.description || "",
135195
},
136196
validation: {
197+
alias: validateAlias,
137198
label: (value) => {
138199
const validation = validateLabel(value || "");
139200
return validation.isValid ? undefined : validation.error;
@@ -174,16 +235,18 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
174235

175236
if (unusedRules.length > 0) {
176237
const ruleToUse = unusedRules[0];
177-
await updateRule(ruleToUse.id, "Quick Alias", "Created using random unused alias");
238+
const quickLabel = config.defaultLabel || "Quick Alias";
239+
const updatedRule = await updateRule(ruleToUse.id, quickLabel, "Created using random unused alias");
178240

241+
await Clipboard.copy(updatedRule.email);
179242
showToast({
180243
style: Toast.Style.Success,
181244
title: "Alias Created",
182-
message: `Successfully created ${ruleToUse.email}`,
245+
message: `Copied ${updatedRule.email} to clipboard`,
183246
primaryAction: {
184247
title: "Copy Email",
185248
onAction: () => {
186-
Clipboard.copy(ruleToUse.email);
249+
Clipboard.copy(updatedRule.email);
187250
},
188251
},
189252
});
@@ -231,7 +294,15 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
231294
</ActionPanel>
232295
}
233296
>
234-
<Form.TextField title="Label" placeholder="Enter a label for this alias (required)" {...itemProps.label} />
297+
{!alias && (
298+
<Form.TextField title="Alias" placeholder="random-slug or name@example.com" autoFocus {...itemProps.alias} />
299+
)}
300+
<Form.TextField
301+
title="Label"
302+
placeholder="Enter a label for this alias (required)"
303+
autoFocus={Boolean(alias)}
304+
{...itemProps.label}
305+
/>
235306
<Form.TextArea
236307
title="Description"
237308
placeholder="Enter a description for this alias (optional)"

0 commit comments

Comments
 (0)