Email

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

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 resolves with ok: false, the SMTP code, and the server's message. A call with no options object or a missing message throws a TypeError or 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 required. If any is missing, the call resolves with ok: false and code: 0. The send uses the app's outbound restrictions and private-address allowlist, as fetch() does. It also counts against the call's outbound fetch limit, so repeated sends can exhaust that budget. message is limited to 16 MB; a larger message is refused with VALUE_TOO_LARGE. Store the SMTP login as secrets and read it from env instead of writing it into function 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

For a password reset, verification, or invite, the function's result is not read and a failure does not reach the caller. A reset request immediately returns 202 {"accepted": true} whether or not the address has an account, so addresses cannot be probed. Read a failed send in the function's logs. Without an email_function, a password reset or verification request returns 409.

A sign-in code request waits for the function's result and can return RECIPIENT_UNSUBSCRIBED when the function refuses the email. See authentication for the response and requirements.

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.