Email: the Inkwell logo in the header
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Android / Core and FFI clippy and tests (push) Skipped
Android / Kotlin + Rust (APK) (push) Skipped
Android / Build the server image (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Python tests (push) Successful in 19s
CI & Build / Web typecheck and unit tests (push) Successful in 10s
CI & Build / integration (push) Successful in 1m35s
CI & Build / Build & push image (push) Successful in 48s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Android / Core and FFI clippy and tests (push) Skipped
Android / Kotlin + Rust (APK) (push) Skipped
Android / Build the server image (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Python tests (push) Successful in 19s
CI & Build / Web typecheck and unit tests (push) Successful in 10s
CI & Build / integration (push) Successful in 1m35s
CI & Build / Build & push image (push) Successful in 48s
The header showed only a yellow dot before the site name. It now shows the app icon, embedded in the message as an inline part (cid:inkwell-logo, the HTML part becoming multipart/related) rather than linked: most clients block remote images, the app fetches nothing (rule 164), and Gmail drops SVG and data: images. The PNG is a copy of frontend/public/icon-192.png, shipped as package data; a test keeps the two identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import re
|
||||
from email import message_from_bytes
|
||||
from email.policy import default
|
||||
from pathlib import Path
|
||||
|
||||
from inkwell import mail_templates
|
||||
from inkwell.mailer import MailSettings, build_message
|
||||
@@ -27,11 +29,19 @@ def test_text_from_settings_and_people_is_escaped_in_the_html():
|
||||
|
||||
def test_the_html_fetches_nothing():
|
||||
"""No remote images or fonts: the app sends with no outbound network, and a remote
|
||||
image in an email is a tracking pixel."""
|
||||
html = mail_templates.password_reset("Inkwell", "a@example.test", LINK).html
|
||||
assert "<img" not in html
|
||||
assert "src=" not in html
|
||||
assert "@import" not in html
|
||||
image in an email is a tracking pixel. The one image is the logo, carried inside
|
||||
the message and named by cid."""
|
||||
email = mail_templates.password_reset("Inkwell", "a@example.test", LINK)
|
||||
assert re.findall(r'src="([^"]*)"', email.html) == [f"cid:{mail_templates.LOGO_CID}"]
|
||||
assert "@import" not in email.html and "<link" not in email.html
|
||||
assert email.images == {mail_templates.LOGO_CID: mail_templates.logo_png()}
|
||||
|
||||
|
||||
def test_the_email_logo_is_the_app_icon():
|
||||
"""A copy, so the package carries it; this keeps the copy from drifting."""
|
||||
icon = Path(__file__).resolve().parents[1] / "frontend" / "public" / "icon-192.png"
|
||||
assert mail_templates.logo_png() == icon.read_bytes()
|
||||
assert mail_templates.logo_png().startswith(b"\x89PNG")
|
||||
|
||||
|
||||
def test_the_message_is_text_first_then_html():
|
||||
@@ -39,5 +49,14 @@ def test_the_message_is_text_first_then_html():
|
||||
msg = build_message(cfg, "a@example.test", mail_templates.test_message("Inkwell", "https://x"))
|
||||
parsed = message_from_bytes(msg.as_bytes(), policy=default)
|
||||
assert parsed.get_content_type() == "multipart/alternative"
|
||||
assert [p.get_content_type() for p in parsed.iter_parts()] == ["text/plain", "text/html"]
|
||||
text, related = parsed.iter_parts()
|
||||
assert text.get_content_type() == "text/plain"
|
||||
# The HTML travels with the logo it shows: multipart/related, html first.
|
||||
assert related.get_content_type() == "multipart/related"
|
||||
html, logo = related.iter_parts()
|
||||
assert html.get_content_type() == "text/html"
|
||||
assert f"cid:{mail_templates.LOGO_CID}" in html.get_content()
|
||||
assert logo.get_content_type() == "image/png"
|
||||
assert logo["Content-ID"] == f"<{mail_templates.LOGO_CID}>"
|
||||
assert logo.get_content() == mail_templates.logo_png()
|
||||
assert parsed["Subject"] == "Inkwell test email"
|
||||
|
||||
Reference in New Issue
Block a user