Ploy
Ploy
Email

Email from workers

Send email from your worker code with the Ploy Email binding.

Email from workers

Workers can send email through Ploy Email with a binding. Ploy sends from your organization's verified domains and applies the same approval, suppression and limits as every other email your organization sends.

Sending requires email access for your organization and a verified sending domain: your <name>.ploy.email domain or a custom domain.

Configuration

Add an email section to your ploy.yaml:

ploy.yaml
kind: worker
build: pnpm build
out: dist
email:
  binding: EMAIL
  from: "Acme <hello@acme.ploy.email>"
  allowed_from:
    - "*@acme.ploy.email"
    - "support@mail.acme.com"
KeyDescription
bindingName of the binding in your worker's env, uppercase with underscores. Required when from or allowed_from is set.
fromDefault sender, hello@acme.com or Acme <hello@acme.com>. Used when send() is called without from.
allowed_fromSenders the worker may use: exact addresses or a whole domain as *@domain. Without it, any address on a verified domain of the organization works.

The settings are taken from your production deployment's ploy.yaml. Preview deployments use the same settings as production.

Run ploy types to add EMAIL: EmailBinding to your worker's Env type.

Sending email

Call send() on the binding:

src/index.ts
export default {
	async fetch(request: Request, env: Env) {
		const { id } = await env.EMAIL.send({
			to: "jane@example.com",
			subject: "Your order has shipped",
			text: "Your order #1234 is on its way.",
			html: "<p>Your order <strong>#1234</strong> is on its way.</p>",
		});
		return Response.json({ id });
	},
};

send() resolves with the ID of the message once Ploy has accepted it for delivery.

OptionTypeDescription
tostring | string[]Recipients. to, cc and bcc together hold at most 50 addresses.
ccstring | string[]Optional copy recipients.
bccstring | string[]Optional blind copy recipients.
fromstringSender. Defaults to email.from. Must be on a verified domain and allowed by allowed_from.
replyTostringOptional Reply-To address.
subjectstringSubject line, required.
textstringPlain-text body. Pass text, html or both.
htmlstringHTML body.
headersobjectExtra headers: In-Reply-To, References, List-Id, List-Unsubscribe, List-Unsubscribe-Post and custom X-* headers.

An email goes out to all of its recipients or to none: if any recipient is refused, for example because it is on the suppression list, nothing is sent. Every recipient counts towards your project's daily limit.

Errors

When Ploy refuses an email, send() throws an error with a code:

try {
	await env.EMAIL.send({ to, subject, text });
} catch (error) {
	if (error instanceof Error && "code" in error) {
		console.warn("Email refused:", error.code, error.message);
	}
	throw error;
}
CodeMeaning
invalid_requestThe message is malformed, for example without a subject or body.
invalid_senderNo sender was given and email.from is not set, or the address is invalid.
sender_not_allowedThe sender is not covered by email.allowed_from.
invalid_recipientA recipient address is invalid.
too_many_recipientsMore than 50 recipients.
access_not_approvedEmail sending is not enabled for your organization.
domain_not_verifiedThe sender's domain is not a verified domain of your organization.
recipient_suppressedA recipient bounced or marked earlier mail as spam.
quota_exceededThe project reached its daily limit.
recipient_rate_limitedToo many emails to the same address within an hour.
send_failedThe email could not be sent. Retrying later may work.

Local development

Under ploy dev, nothing is sent. send() checks the message against your ploy.yaml like in production, and every email lands in the local inbox: open the dev dashboard and go to Email to read it.

How is this guide?

Last updated on