Skip to content

Commit

Permalink
enable the use of send email bindings (#2914)
Browse files Browse the repository at this point in the history
Add support for adding send email bindings to worker scripts. These
bindings can have 3 different types:
- Unrestricted
- Targeted
- Allowlist
  • Loading branch information
edevil committed Mar 21, 2023
1 parent e062180 commit 9af1a64
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .changeset/tame-bats-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"wrangler": minor
---

feat: add support for send email bindings

Support send email bindings in order to send emails from a worker. There
are three types of bindings:

- Unrestricted: can send email to any verified destination address.
- Restricted: can only send email to the supplied destination address (which
does not need to be specified when sending the email but also needs to be a
verified destination address).
- Allowlist: can only send email to the supplied list of verified destination
addresses.
45 changes: 45 additions & 0 deletions packages/wrangler/src/__tests__/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe("normalizeAndValidateConfig()", () => {
jsx_fragment: "React.Fragment",
tsconfig: undefined,
kv_namespaces: [],
send_email: [],
legacy_env: true,
logfwdr: {
bindings: [],
Expand Down Expand Up @@ -903,6 +904,17 @@ describe("normalizeAndValidateConfig()", () => {
preview_id: "KV_PREVIEW_1",
},
],
send_email: [
{ name: "SEB_TARGET", destination_address: "teste@example.com" },
{ name: "SEB_UNRESTRICTED" },
{
name: "SEB_ALLOWLIST",
allowed_destination_addresses: [
"email1@example.com",
"email2@example.com",
],
},
],
r2_buckets: [
{ binding: "R2_BINDING_1", bucket_name: "R2_BUCKET_1" },
{
Expand Down Expand Up @@ -1629,6 +1641,39 @@ describe("normalizeAndValidateConfig()", () => {
});
});

it("should error if send_email.bindings are not valid", () => {
const { diagnostics } = normalizeAndValidateConfig(
{
send_email: [
{},
{ binding: "VALID" },
{ name: "SEB", destination_address: 123 },
{
name: "SEB2",
allowed_destination_addresses: 123,
},
{
name: "SEB3",
destination_address: "email@example.com",
allowed_destination_addresses: ["email@example.com"],
},
],
} as unknown as RawConfig,
undefined,
{ env: undefined }
);

expect(diagnostics.hasWarnings()).toBe(false);
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- \\"send_email[0]\\" bindings should have a string \\"name\\" field but got {}.
- \\"send_email[1]\\" bindings should have a string \\"name\\" field but got {\\"binding\\":\\"VALID\\"}.
- \\"send_email[2]\\" bindings should, optionally, have a string \\"destination_address\\" field but got {\\"name\\":\\"SEB\\",\\"destination_address\\":123}.
- \\"send_email[3]\\" bindings should, optionally, have a []string \\"allowed_destination_addresses\\" field but got {\\"name\\":\\"SEB2\\",\\"allowed_destination_addresses\\":123}.
- \\"send_email[4]\\" bindings should have either a \\"destination_address\\" or \\"allowed_destination_addresses\\" field, but not both."
`);
});

describe("[d1_databases]", () => {
it("should error if d1_databases is an object", () => {
const { diagnostics } = normalizeAndValidateConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function createWorkerBundleFormData(workerBundle: BundleResult): FormData {
bindings: {
vars: undefined,
kv_namespaces: undefined,
send_email: undefined,
wasm_modules: undefined,
text_blobs: undefined,
data_blobs: undefined,
Expand Down
18 changes: 18 additions & 0 deletions packages/wrangler/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,24 @@ interface EnvironmentNonInheritable {
preview_id?: string;
}[];

/**
* These specify bindings to send email from inside your Worker.
*
* NOTE: This field is not automatically inherited from the top level environment,
* and so must be specified in every named environment.
*
* @default `[]`
* @nonInheritable
*/
send_email: {
/** The binding name used to refer to the this binding */
name: string;
/** If this binding should be restricted to a specific verified address */
destination_address?: string;
/** If this binding should be restricted to a set of verified addresses */
allowed_destination_addresses?: string[];
}[];

/**
* Specifies Queues that are bound to this Worker environment.
*
Expand Down
18 changes: 18 additions & 0 deletions packages/wrangler/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
data_blobs,
durable_objects,
kv_namespaces,
send_email,
queues,
d1_databases,
r2_buckets,
Expand Down Expand Up @@ -155,6 +156,23 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
});
}

if (send_email !== undefined && send_email.length > 0) {
output.push({
type: "Send Email",
entries: send_email.map(
({ name, destination_address, allowed_destination_addresses }) => {
return {
key: name,
value:
destination_address ||
allowed_destination_addresses?.join(", ") ||
"unrestricted",
};
}
),
});
}

if (queues !== undefined && queues.length > 0) {
output.push({
type: "Queues",
Expand Down
57 changes: 57 additions & 0 deletions packages/wrangler/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,16 @@ function normalizeAndValidateEnvironment(
validateBindingArray(envName, validateKVBinding),
[]
),
send_email: notInheritable(
diagnostics,
topLevelEnv,
rawConfig,
rawEnv,
envName,
"send_email",
validateBindingArray(envName, validateSendEmailBinding),
[]
),
queues: notInheritable(
diagnostics,
topLevelEnv,
Expand Down Expand Up @@ -1765,6 +1775,53 @@ const validateKVBinding: ValidatorFn = (diagnostics, field, value) => {
return isValid;
};

const validateSendEmailBinding: ValidatorFn = (diagnostics, field, value) => {
if (typeof value !== "object" || value === null) {
diagnostics.errors.push(
`"send_email" bindings should be objects, but got ${JSON.stringify(
value
)}`
);
return false;
}
let isValid = true;
// send email bindings must have a name.
if (!isRequiredProperty(value, "name", "string")) {
diagnostics.errors.push(
`"${field}" bindings should have a string "name" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}
if (!isOptionalProperty(value, "destination_address", "string")) {
diagnostics.errors.push(
`"${field}" bindings should, optionally, have a string "destination_address" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}
if (!isOptionalProperty(value, "allowed_destination_addresses", "object")) {
diagnostics.errors.push(
`"${field}" bindings should, optionally, have a []string "allowed_destination_addresses" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}
if (
"destination_address" in value &&
"allowed_destination_addresses" in value
) {
diagnostics.errors.push(
`"${field}" bindings should have either a "destination_address" or "allowed_destination_addresses" field, but not both.`
);
isValid = false;
}
return isValid;
};

const validateQueueBinding: ValidatorFn = (diagnostics, field, value) => {
if (typeof value !== "object" || value === null) {
diagnostics.errors.push(
Expand Down
17 changes: 17 additions & 0 deletions packages/wrangler/src/create-worker-upload-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export type WorkerMetadataBinding =
| { type: "text_blob"; name: string; part: string }
| { type: "data_blob"; name: string; part: string }
| { type: "kv_namespace"; name: string; namespace_id: string }
| {
type: "send_email";
name: string;
destination_address?: string;
allowed_destination_addresses?: string[];
}
| {
type: "durable_object_namespace";
name: string;
Expand Down Expand Up @@ -105,6 +111,17 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
});
});

bindings.send_email?.forEach(
({ name, destination_address, allowed_destination_addresses }) => {
metadataBindings.push({
name: name,
type: "send_email",
destination_address,
allowed_destination_addresses,
});
}
);

bindings.durable_objects?.bindings.forEach(
({ name, class_name, script_name, environment }) => {
metadataBindings.push({
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,7 @@ function getBindings(
),
...(args.kv || []),
],
send_email: configParam.send_email,
// Use a copy of combinedVars since we're modifying it later
vars: {
...getVarsForDev(configParam, env),
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/publish/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
? { binding: "__STATIC_CONTENT", id: assets.namespace }
: []
),
send_email: config.send_email,
vars: { ...config.vars, ...props.vars },
wasm_modules: config.wasm_modules,
text_blobs: {
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/secret/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export const secret = (secretYargs: CommonYargsArgv) => {
},
bindings: {
kv_namespaces: [],
send_email: [],
vars: {},
durable_objects: { bindings: [] },
queues: [],
Expand Down
10 changes: 10 additions & 0 deletions packages/wrangler/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ export interface CfKvNamespace {
id: string;
}

/**
* A binding to send email.
*/
export interface CfSendEmailBindings {
name: string;
destination_address?: string;
allowed_destination_addresses?: string[];
}

/**
* A binding to a wasm module (in service-worker format)
*/
Expand Down Expand Up @@ -216,6 +225,7 @@ export interface CfWorkerInit {
bindings: {
vars: CfVars | undefined;
kv_namespaces: CfKvNamespace[] | undefined;
send_email: CfSendEmailBindings[] | undefined;
wasm_modules: CfWasmModuleBindings | undefined;
text_blobs: CfTextBlobBindings | undefined;
data_blobs: CfDataBlobBindings | undefined;
Expand Down

0 comments on commit 9af1a64

Please sign in to comment.