gitoriaLog in with ident

ident

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit7423581574235815ident mission 009 (4/4): docs (README files map + same-output test, STATUS, LOG), report, tests/letcount.py + realdata-baseline/comparemre74235815/lib/mail.hl

3.1 KB

  1. // lib/mail.hl — THE MAILER. One hl:smtp Mailer, configured from `.env` beside project.hl
  2. // (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM; the real environment
  3. // outranks the file). A send is non-blocking: the SMTP conversation runs on the
  4. // mailer's thread and its outcome arrives on the event loop (logged below).
  5. //
  6. // IDENT_MAIL_SINK names a file that receives the mail INSTEAD of the SMTP server: one
  7. // line `<address> <code>` is APPENDED per code. The dev server and the gate use it, so
  8. // no real mail is sent until SMTP is configured for mail.byrod.in (see README).
  9. //
  10. // Handlers belong to instances: project.hl constructs this file once (`mail = new Mail()`).
  11. import { Mailer } from 'hl:smtp'
  12. import { env } from 'hl:proc'
  13. import { writeFile, readFile, exists } from 'hl:fs'
  14. static NL = '
  15. '
  16. static setting = (name, fallback) => { return env(name) != null && env(name) != '' ? env(name) : fallback }
  17. // An unconfigured dev setup gets the placeholder host 127.0.0.1 (the Mailer must exist:
  18. // `mailer.deliver()` and the `on mailer.…` handlers below); with the sink set nothing is ever
  19. // sent through it. SMTP_FROM's default is a real sender, not a placeholder — kept (mission 035).
  20. static smtpHost = setting('SMTP_HOST', null)
  21. static mailer = new Mailer(smtpHost != null ? smtpHost : '127.0.0.1', toNumber(setting('SMTP_PORT', '587')), {
  22. user = setting('SMTP_USER', null)
  23. pass = setting('SMTP_PASSWORD', null)
  24. sender = setting('SMTP_FROM', '[email protected]')
  25. })
  26. static sink = env('IDENT_MAIL_SINK') != null && env('IDENT_MAIL_SINK') != '' ? env('IDENT_MAIL_SINK') : null
  27. static sendCode = (email, code) => {
  28. if (sink != null) {
  29. before = exists(sink) ? readFile(sink) : ''
  30. writeFile(sink, before + email + ' ' + code + NL)
  31. return 0
  32. }
  33. if (smtpHost == null) {
  34. console.warn('mail: NOT SENT to ' + email + ' — neither IDENT_MAIL_SINK nor SMTP_HOST is set')
  35. return 0
  36. }
  37. return mailer.send({
  38. to = email
  39. subject = 'Your ident login code: ' + code
  40. text = 'Hi,
  41. your one-time login code for worldapi is ' + code + '
  42. It is valid for 10 minutes and works once. If you did not ask for it, ignore this mail.
  43. '
  44. })
  45. }
  46. // AN INVITATION MAIL (ident#22). With the sink set, the line `<address> <link>` goes to
  47. // `<sink>.invites` (a second file: the code reader of the gates keeps its own format).
  48. static sendInvite = (email, link, appName, project, role, days) => {
  49. if (sink != null) {
  50. f = sink + '.invites'
  51. before = exists(f) ? readFile(f) : ''
  52. writeFile(f, before + email + ' ' + link + NL)
  53. return 0
  54. }
  55. if (smtpHost == null) {
  56. console.warn('mail: invitation NOT SENT to ' + email + ' — neither IDENT_MAIL_SINK nor SMTP_HOST is set')
  57. return 0
  58. }
  59. return mailer.send({
  60. to = email
  61. subject = 'You are invited to ' + project + ' (' + appName + ')'
  62. text = 'Hi,
  63. you are invited to ' + project + ' in ' + appName + ' (role: ' + role + ').
  64. Open this link to join with ident:
  65. ' + link + '
  66. The link works for ' + days + ' days. If you were not expecting it, ignore this mail.
  67. '
  68. })
  69. }
  70. mailer.deliver()
  71. on mailer.sent(r) { console.log('mail: sent', r.id, r.to, r.message) }
  72. on mailer.failed(r) { console.warn('mail: FAILED', r.id, r.to, r.stage, r.code, r.message) }

Branches

Latest commits

  • 74235815ident mission 009 (4/4): docs (README files map + same-output test, STATUS, LOG), report, tests/letcount.py + realdata-baseline/comparemre
  • fe183516ident mission 009 (3/4): let only where reassigned — 293 never-reassigned lets are plain declarations; kept: reassigned, loop bodies, names of a file member, a name declared twice in one function; same outputmre
  • d2e7f91bident mission 009 (2/4): one lib file per topic (login, accounts, identities, apps, invites, selector, notify + helpers, util), function routes as thin wrappers in lib/api.hl, project.hl = the map; same outputmre
  • 91017164ident mission 009 (1/4): file moves — the root .hl files into lib/ (api.hl → lib/api-helpers.hl), styles.hl → components/styles.hl; imports adjusted, no other changemre
  • f8bdcbc2ident: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); all gates greenmre
  • ff78726cident: Hybriel master 190aa11d (fc838894 GC correctness, #127, #126 closure scopes); gates all greenmre
  • a3a7d21aident: Hybriel master 8efba065 (#126 GC by bytes, #48 lambda params copy); session-writing lambdas take &sessionmre
  • 98226b41antcolony#40: mission references point to the moved missionsmre
  • ff805b9aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • 51a7bcdfident: Hybriel master 73267707 (#122); /code uses the new page() signature; pending address passed as parameter; once-checksmre
  • 836f644fident#24: installable app (manifest, service worker, data-free offline /start), own iconmre
  • 8bebbbf2deploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • cc063ea2deploy.sh: never send .git or .gitignore to Byrodinmre
  • 81b15b7bState of 2026-09-27, before the move to gitoriamre