Feature request: built-in SMTP email transport in core #1777
khoinguyenpham04
announced in
Roadmap
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
This Roadmap discussion mirrors #1541: Feature request: built-in SMTP email transport in core.
Use this discussion to upvote the roadmap item and discuss priority, use cases, and product feedback. Keep implementation tracking, reproduction details, and PR-specific feedback on the source issue.
Original Issue
Feature request: built-in SMTP email transport in Emdash core
Summary
Provide a first-class SMTP email transport in Emdash core so operators can
send mail through any standard SMTP server (host / port / username / password)
instead of only through HTTP transactional APIs. A plugin cannot do this, by
design — only the host runtime can open the TCP socket SMTP requires.
Motivation
Today, sending email from Emdash on Cloudflare Workers means using a provider
with an HTTP API (e.g. the
emdash-plugin-brevoplugin, which callsPOST https://api.brevo.com/v3/smtp/email). That works well, but a large classof operators only have generic SMTP credentials:
smtp-relay.brevo.com:587, authenticated with anxsmtpsib-SMTP key) — distinct from its HTTP API key (xkeysib-).None of these can be reached from a plugin.
Why this can't be a plugin
Emdash sandboxed plugins are given exactly one network primitive —
ctx.http.fetch()— which speaks HTTP/HTTPS only and is gated by theplugin's
allowedHostsallowlist. There is no TCP/socket capability in theplugin capability vocabulary. SMTP is a raw TCP protocol (EHLO / STARTTLS /
AUTH / MAIL FROM / RCPT TO / DATA), so a plugin physically cannot open an SMTP
connection.
Cloudflare Workers can open TCP sockets via
connect()fromcloudflare:sockets, but only host code can — that module is not exposed tothe sandbox. Therefore an SMTP transport has to live in core (or in a
host-trusted/native extension point), not in a sandboxed plugin.
Proposal
Add an optional core email transport,
smtp, that registers as the emaildelivery provider so existing
ctx.email.send()callers and theemail:deliverhook contract keep working unchanged.Configuration
Via env/wrangler vars (secrets) and/or the admin Email settings page:
EMAIL_SMTP_HOSTsmtp-relay.brevo.comEMAIL_SMTP_PORT587EMAIL_SMTP_SECUREstarttls|tlsEMAIL_SMTP_USERyou@example.comEMAIL_SMTP_PASSxsmtpsib-…EMAIL_SMTP_FROMSite <noreply@example.com>Implementation sketch
connect(\${host}:${port}`, { secureTransport: port === 465 ? "on" : "starttls", allowHalfOpen: false })fromcloudflare:sockets`.EHLO→ (STARTTLS+socket.startTls()whensecure=starttls) →AUTH LOGIN/PLAIN→MAIL FROM→RCPT TO→DATA→ dot-stuffed MIME body →QUIT.text/plain+text/htmlmultipart/alternative, quoted-printable/base64, message-id, date).Caveats to call out in the implementation
ports 587/465 only, and should validate/refuse 25 with a clear message.
never fall back to plaintext auth on an unencrypted channel.
document this alongside the existing dynamic-egress-IP guidance.
Alternatives considered
a third party in the mail path and means handing SMTP credentials to that
service. Rejected as a default.
the reason for this request.
API users, but excludes everyone who only has SMTP credentials.
Backward compatibility
Additive. Existing HTTP-API providers (including the Brevo plugin) continue to
work;
smtpis just another selectable transport. If both a core SMTP transportand an exclusive
email:deliverplugin are configured, document the precedence(suggest: an explicitly-configured plugin transport wins, else core SMTP).
Acceptance criteria
ctx.email.send()delivers via SMTP when thesmtptransport is configured.Beta Was this translation helpful? Give feedback.
All reactions