Skip to content

Backend: email -- SMTP and SES, templates, an outbox, password reset, verification and mailed sign-in codes - #6215

Merged
shai-almog merged 1 commit into
masterfrom
backend-mail
Nov 11, 2026
Merged

shai-almog merged 1 commit into
masterfrom
backend-mail

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The backend can now send email, and the security package uses it for the three flows every application ends up writing by hand.

Sending

mail.send(Mail.to("ada@example.com")
        .subject("Your receipt")
        .template("mail/receipt", model)
        .attach("receipt.pdf", "application/pdf", pdf)
        .build());
  • One class where Spring has four. Mail is immutable and validated in build(); MailSender is injected by constructor type.
  • Transports by URL. cn1.mail.url=smtp://…, smtps://…, ses://eu-west-1, log: for development and memory: for tests. The build reads the property and links only that transport.
  • SMTP in pure Java over the runtime's own sockets: STARTTLS (required unless switched off, and a password never crosses a clear connection), PLAIN / LOGIN / XOAUTH2, pipelining, SMTPUTF8, a small connection pool. SendGrid, Mailgun, Postmark and Gmail are all reached this way.
  • SES by its API, signed with the machine's own credentials.
  • MIME encoded once: quoted-printable text, wrapped Base64, RFC 2047 headers, RFC 2231 file names, multipart/alternative, related and mixed nested as needed.
  • Templates under templates/mail render through the generated views; a .txt beside the .html becomes the plain alternative, otherwise it is derived.

Delivery

With a data source, send stages the mail in cn1_mail_outbox inside the caller's transaction: a rolled back order mails no receipt, and a committed one is sent even if the server stops before the transport answers. Failures retry with back-off; a final refusal moves the mail to cn1_mail_dead. Several servers share the table without sending a mail twice.

Counters, a span per send (recipient count, never an address) and a health entry come with it.

Security flows

  • Password reset: http.passwordReset(...). The request page answers the same whether or not the account exists; the link is single use and only its hash is stored. The generated login page gains a "Forgot your password?" link whenever a reset page is configured.
  • Email verification: a mailed link, confirmed by a button rather than by opening the link (mail scanners open links), and .verifiedEmail() as an authorization rule.
  • Mailed sign-in code as a second factor beside TOTP: six digits, ten minutes, five tries.

Testing

@BackendTest captures mail in memory; MailSender.sent() exposes it. MailCheck runs one body on the JVM and natively against an in-process SMTP server that asserts on the bytes received, on SQLite, PostgreSQL and MySQL. BackendMailTest runs it in CI.

Docs

A new Mail chapter in the developer guide, and password reset, verification and mailed codes in the Security chapter.

🤖 Generated with Claude Code

…y flows on top

A mail API for the backend runtime (com.codename1.backend.mail): MailSender
and an immutable Mail, MIME written once (quoted-printable, wrapped Base64,
encoded words, multipart nesting), and transports chosen by cn1.mail.url --
smtp:// and smtps:// (STARTTLS, PLAIN/LOGIN/XOAUTH2, pipelining, a small
connection pool), ses:// over the signed API, memory: for tests and log: for
development. Only the transport the application names is linked.

With a data source a mail is staged in cn1_mail_outbox inside the caller's
transaction and sent after commit, retried with back-off, and buried in
cn1_mail_dead when it is refused for good. Templates under templates/mail
render through the generated views.

Security: password reset, email verification and a mailed sign-in code as a
second factor, all on single-use hashed tokens. The generated login page
links to the reset page when one is configured.

MailCheck runs one body on the JVM and natively against an in-process SMTP
server, and on SQLite, PostgreSQL and MySQL; BackendMailTest runs it in CI.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@shai-almog
shai-almog merged commit bc0ef9c into master Nov 11, 2026
46 of 55 checks passed
@shai-almog
shai-almog deleted the backend-mail branch November 11, 2026 17:32
@github-actions

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ✅ ByteCodeTranslator: 0 findings (no issues)
    • ✅ android: 0 findings (no issues)
    • ✅ android-compat: 0 findings (no issues)
    • ✅ android-res-compiler: 0 findings (no issues)
    • ✅ backend: 0 findings (no issues)
    • ✅ backend-test: 0 findings (no issues)
    • ✅ build-engine: 0 findings (no issues)
    • ✅ build-hint-catalog: 0 findings (no issues)
    • ✅ build-hint-tools: 0 findings (no issues)
    • ✅ codenameone-gradle-plugin: 0 findings (no issues)
    • ✅ codenameone-maven-plugin: 0 findings (no issues)
    • ✅ compose-compat: 0 findings (no issues)
    • ✅ core-unittests: 0 findings (no issues)
    • ✅ desktop-compat-runtime: 0 findings (no issues)
    • ✅ fxml-compiler: 0 findings (no issues)
    • ✅ ios: 0 findings (no issues)
    • ✅ javac: 0 findings (no issues)
    • ✅ javafx-compat: 0 findings (no issues)
    • ✅ project-model: 0 findings (no issues)
    • ✅ python-runtime: 0 findings (no issues)
    • ✅ python-transpiler: 0 findings (no issues)
    • ✅ swing-compat: 0 findings (no issues)
  • ✅ PMD: 0 findings (no issues) [Report archive]
  • ✅ Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 231 screenshots: 231 matched.
✅ JavaScript-port screenshot tests passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant