Email

Send mail from a function over SMTP, and name the function that sends your app's sign-in, verification and invite mail.

Docs menu

Sending mail from a function

substrio.mail.smtp(options) sends one message over SMTP and resolves with { ok, code, message }: a server refusal never throws, it settles ok: false with the SMTP code and the server's message. Only a call with no options object, or one missing message, throws (a TypeError or a MISSING_FIELD error).

export default async function (request) {
  const result = await substrio.mail.smtp({
    host: "smtp.example.com",
    port: 587,
    user: env.SMTP_USER,
    pass: env.SMTP_PASS,
    from: "[email protected]",
    to: "[email protected]",
    message: "Subject: Your order shipped\r\nFrom: [email protected]\r\nTo: [email protected]\r\n\r\nOn its way.\r\n",
  });
  return Response.json(result);
}
Option Type Default Meaning
host string (required) the SMTP server
port number 587, or 465 when secure the SMTP port
secure boolean false TLS from the first byte (port 465) instead of STARTTLS
user, pass string none SMTP login
from string (required) the envelope sender
to string or string[] (required) one or more recipients, each its own RCPT
message string (required) the whole message, headers and body (RFC 5322)
requireTLS boolean true refuse a server with no STARTTLS when secure is false
verifyTLS boolean true verify the server's certificate
encode boolean true normalize line endings to CRLF
timeoutMs number none the send's timeout

host, from and at least one to address are all required: missing any of them settles ok: false with code: 0. The send runs behind your app's outbound fence, the same private-address allowlist a fetch() call runs behind, and counts against the call's outbound fetch limit: a loop of sends can exhaust that call's fetch budget. message is limited to 16MB; a larger one is refused with a VALUE_TOO_LARGE error. Store the SMTP login as secrets and read it from env, rather than writing it into the function's source.

The mail your app's sign-in flow sends

Substrio does not send mail on its own behalf. Name one of your functions as email_function in the [auth] table of substrio.toml, and it is called whenever a sign-in flow needs a mail sent: a password reset, an email verification, or an account invite.

[auth]
email_function = "send_auth_mail"
reset_url = "https://app.example/reset?token={token}"
verify_url = "https://app.example/verify?token={token}"
export default async function send_auth_mail(event) {
  // event: { kind, email, token, link, expires_at, app, user_id }
  await substrio.mail.smtp({
    host: "smtp.example.com",
    user: env.SMTP_USER,
    pass: env.SMTP_PASS,
    from: "[email protected]",
    to: event.email,
    message: `Subject: Confirm your action\r\nFrom: [email protected]\r\nTo: ${event.email}\r\n\r\n${event.link}\r\n`,
  });
}
Field Meaning
kind password_reset, email_verify or account_invite
email where to send it
token the single-use token, redeemed by POST /v1/_auth/password/reset or /v1/_auth/email/verify
link reset_url or verify_url with {token} filled in (an invite's link comes from invite_url); empty when the setting is empty
expires_at when the token stops working, in Unix seconds
app the app's slug
user_id the user the token was issued to (absent for account_invite)
account_name, inviter_email, role present only for account_invite, in place of user_id

What email_function returns is not read, and a failure inside it does not reach the caller: a reset request answers 202 {"accepted": true} at once whether or not the address has an account, so an address cannot be probed. Read a failed send from the function's own logs. With no email_function set, a password reset or verification request answers 409.

curl -s -X POST https://my-app.substrio.com/v1/_auth/password/reset_request \
  -H "Content-Type: application/json" -d '{"email": "[email protected]"}'

See authentication for the full sign-in, reset and invite flow, and the routes that redeem a token.