openvibers/openvibe.mail Commits

openvibe.email's home says what Mail does now, and follows the sending switch (#33)

OP OpenVibers committed 5 days ago · 7601c147ab · 4 files, +60 −9

The home page still said "Receive only: there is no way to send" and "Sending and your own domain come next"
a day after your own address, aliases, custom domains and IMAP pull went live. It now leads with your address and
domain, adds a Your address card and an Inbox AI card (off until you turn it on; a draft is never sent until you
press Send), and the sending lines follow MAIL_OUTBOUND: "opens next" while it is off, the allowance once it is on.
The words live in server/http/mail-copy.js so the agent files can say the same thing.

Changes

ddiff --git a/server/http/mail-copy.js b/server/http/mail-copy.js
nnew file mode 100644
iindex 0000000..431eabd
---- /dev/null
++++ b/server/http/mail-copy.js
@@@ -0,0 +1,28 @@
++'use strict';
++/**
++ * What openvibe.email says about itself, in one place, so the home page, its description and the agent files say the
++ * same thing. Sending to other providers follows MAIL_OUTBOUND (config.outbound.enabled): the lines about it change
++ * with the switch instead of going stale the day it is turned on. (Until 11-09 the page still said "Receive only" and
++ * "your own domain comes next" a day after addresses, aliases, custom domains and forwarding went live.)
++ */
++function mailCopy(config) {
++ const sending = !!(config && config.outbound && config.outbound.enabled);
++ const what = 'Your own address and domain, masks for every sign-up, ten-minute addresses that show you the code, and inboxes your agents can read.';
++ return {
++ sending,
++ summary: what,
++ // Under 160 characters: the page shell cuts a longer description.
++ description: sending
++ ? 'Your own address and domain, masks, ten-minute addresses and inboxes for your agents, with plain-text sending.'
++ : 'Your own address and domain, masks, ten-minute addresses and inboxes for your agents. Mail arrives today; sending opens next.',
++ // The "Your address" card's last line and the rules' first line.
++ sendLine: sending
++ ? 'Write and reply from it in plain text (up to 10 recipients a message, 20 a day to start, rising with your history), and forward a copy to an address you verified.'
++ : 'Mail between OpenVibe addresses works now; sending and forwarding to other providers open next.',
++ sendRule: sending
++ ? 'Sending is plain text with a daily allowance and no bulk lists, and every message is checked for spam before it leaves.'
++ : 'Sending to other providers is closed for now. When it opens, every message is checked for spam before it leaves.',
++ };
++}
++
++module.exports = { mailCopy };
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 6f517c1..ffcb3a5 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -63,8 +63,13 @@ const CSP = [
].join('; ');
const BODY = fs.readFileSync(path.join(__dirname, '..', 'views', 'home.html'), 'utf8');
++const { mailCopy } = require('./mail-copy');
++const escHtml = (v) => String(v).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
function homePage(config) {
++ const words = mailCopy(config);
++ const body = BODY.replace('{{LEAD}}', escHtml(words.summary)).replace('{{SEND_LINE}}', escHtml(words.sendLine))
++ .replace('{{SEND_RULE}}', escHtml(words.sendRule));
const base = String(config.baseUrl || 'https://openvibe.email').replace(/\/+$/, '');
const nav = {
service: SERVICE,
@@@ -82,7 +87,7 @@ function homePage(config) {
lang: 'en',
title: `${SITE_NAME}: an address for every sign-up`,
siteName: SITE_NAME,
-- description: 'Ten-minute addresses that show you the code, and inboxes your agents can read. Receive only: nothing here sends mail.',
++ description: words.description,
canonical: `${base}/`,
robots: 'index, follow',
jsonLd: [{ '@context': 'https://schema.org', '@type': 'WebApplication', name: SITE_NAME, url: `${base}/`, applicationCategory: 'CommunicationApplication', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } }],
@@@ -96,7 +101,7 @@ function homePage(config) {
<script src="${asset('js/mail.js')}" defer></script>`,
body: `<a class="skip" href="#main">Skip to content</a>
<div id="navbar-mount"></div>
--${BODY}
++${body}
<script>
window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};
document.addEventListener('DOMContentLoaded', function () {
ddiff --git a/server/views/home.html b/server/views/home.html
iindex 2cd37fe..8e268a7 100644
---- a/server/views/home.html
++++ b/server/views/home.html
@@@ -2,7 +2,7 @@
<header class="ml-head">
<p class="ml-kicker">OpenVibe.Email</p>
<h1>An address for every sign-up</h1>
--<p class="ml-lead">Throwaway addresses, private mailboxes and Inbox AI that leaves every reply for you to review and send.</p>
++<p class="ml-lead">{{LEAD}}</p>
</header>
<section class="ml-burner" data-mail-burner aria-labelledby="ml-burner-h">
@@@ -33,17 +33,18 @@
<section aria-labelledby="ml-more-h">
<h2 id="ml-more-h" class="ml-sr">What else it does</h2>
<div class="ml-grid">
--<article class="ml-card"><h3>Inboxes for your agents</h3><p>Give an agent its own address for sign-ups and codes, so it never needs yours. It reads only what arrives there.</p></article>
++<article class="ml-card"><h3>Your address</h3><p>A name at openvibe.email, aliases, and your own domain, all into one inbox with trackers off until you say. Bring another mailbox in over IMAP, read only. {{SEND_LINE}}</p><p><a class="ml-card-link" href="/inbox">Open your inbox</a></p></article>
<article class="ml-card"><h3>Masks</h3><p>An address per site that delivers to your inbox, so a leak or a spammer never learns your real one. One switch turns it off.</p><p><a class="ml-card-link" href="/inbox">Make one in your inbox</a></p></article>
--<article class="ml-card"><h3>Your inbox</h3><p>Read what came to your masks and your agents' inboxes, with trackers off until you say. Sending and your own domain come next.</p><p><a class="ml-card-link" href="/inbox">Open your inbox</a></p></article>
++<article class="ml-card"><h3>Inboxes for your agents</h3><p>Give an agent its own address for sign-ups and codes, so it never needs yours. It reads only what arrives there.</p></article>
++<article class="ml-card"><h3>Inbox AI, if you want it</h3><p>Off until you turn it on: summaries of long threads and reply drafts. A draft is never sent until you edit it and press Send.</p></article>
</div>
</section>
<section aria-labelledby="ml-rules-h">
<h2 id="ml-rules-h" class="ml-h2">How it treats mail</h2>
<ul class="ml-rules">
--<li>Receive only: there is no way to send from these addresses.</li>
--<li>A message body is read only to find a code, and it is never stored outside the mailbox or shown here.</li>
++<li>{{SEND_RULE}}</li>
++<li>A message body stays in its mailbox: never in a log, an event or this page. For a ten-minute address, only the code, the sender and the time are shown.</li>
<li>Expired addresses bounce, and their mail is deleted.</li>
</ul>
</section>
ddiff --git a/test/page.test.js b/test/page.test.js
iindex d4a7cc8..ffba35a 100644
---- a/test/page.test.js
++++ b/test/page.test.js
@@@ -34,10 +34,15 @@ test('/ is the page: the ten-minute address, the honest cards, the navbar, a str
for (const marker of ['data-mail-burner', 'Get a 10-minute address', 'data-mail-mine', 'Inboxes for your agents', 'navbar-mount', '/shared/']) {
assert.ok(r.text.includes(marker), marker);
}
-- // Masks and the inbox work since M1.5: no card says Later, and both lead to /inbox; sending is said to come next.
++ // Masks, your address and the inbox work: no card says Later, and two lead to /inbox. With MAIL_OUTBOUND off (the
++ // default) the page says sending to other providers opens next and never "Receive only" or "your own domain comes
++ // next" (both stale since 11-08); the description says the same.
assert.strictEqual((r.text.match(/class="ml-soon">Later</g) || []).length, 0);
assert.strictEqual((r.text.match(/href="\/inbox"/g) || []).length, 2);
-- assert.match(r.text, /Sending and your own domain come next/);
++ assert.match(r.text, /Your address<\/h3><p>A name at openvibe\.email, aliases, and your own domain/);
++ assert.match(r.text, /sending and forwarding to other providers open next/);
++ assert.match(r.text, /<meta name="description" content="[^"…]*Mail arrives today; sending opens next\.">/);
++ assert.doesNotMatch(r.text, /Receive only|your own domain come next|\{\{[A-Z_]+\}\}/);
assert.doesNotMatch(r.text, /<form[^>]*action="\/api/, 'nothing posts to the API without the script');
const csp = r.headers.get('content-security-policy') || '';
assert.match(csp, /frame-ancestors 'none'/);
@@@ -83,3 +88,15 @@ test('/release.json is the release manifest open tabs poll, never cached', async
assert.ok(typeof m.release === 'string' && m.release.length > 0, 'the deployed commit');
assert.ok(m.packages && m.packages['openvibe-shared'], 'the platform packages it runs');
});
++
++test('with sending on, the page says how sending works instead of "opens next"', () => {
++ const { mailCopy } = require('../server/http/mail-copy');
++ const on = mailCopy({ outbound: { enabled: true } });
++ const off = mailCopy({ outbound: { enabled: false } });
++ assert.match(on.sendLine, /up to 10 recipients a message, 20 a day to start/);
++ assert.match(on.sendRule, /checked for spam before it leaves/);
++ assert.doesNotMatch(`${on.description} ${on.sendLine} ${on.sendRule}`, /opens? next|closed/);
++ assert.match(off.sendRule, /closed for now/);
++ assert.strictEqual(on.summary, off.summary, 'what the site is does not change with the switch');
++ for (const d of [on.description, off.description]) assert.ok(d.length <= 158, `the description fits the shell: ${d.length}`);
++});