Feat: Use newsletter.planetrenox.com for sub/unsub (lynchmark pool)

Subscribe form now posts to newsletter.planetrenox.com/api/sub with a
Turnstile token; unsubscribe posts to /api/unsub. Drops the old Resend
Pages functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 02:18:08 +00:00
co-authored by Claude Opus 5.5
parent a55bfead8d
commit 88aad3106c
4 changed files with 9 additions and 61 deletions
+8 -3
View File
@@ -36,6 +36,7 @@
<form id="sub-form" class="space-y-4 pt-4 border-t border-gray-100">
<input type="email" id="email" required placeholder="you@example.com" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none mono text-sm">
<button type="submit" class="w-full px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition">Subscribe</button>
<div class="cf-turnstile" data-sitekey="0x4AAAAAAFG4HWETOcDDQ5vg" data-appearance="interaction-only" data-size="flexible"></div>
</form>
<p id="msg" class="text-center text-sm hidden"></p>
</div>
@@ -43,19 +44,23 @@
<footer class="mt-12 text-center text-xs text-gray-500 mono">Public Domain</footer>
</main>
<script>
const form=document.getElementById('sub-form'),msg=document.getElementById('msg');
const form=document.getElementById('sub-form'),msg=document.getElementById('msg'),
token=()=>form.querySelector('[name=cf-turnstile-response]')?.value;
form.onsubmit=async e=>{
e.preventDefault();
msg.className='text-center text-sm text-gray-500';
msg.textContent='Subscribing...';
msg.classList.remove('hidden');
for(let i=0;i<50&&!token();i++)await new Promise(r=>setTimeout(r,100));
try{
const r=await fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:document.getElementById('email').value})});
const r=await fetch('https://newsletter.planetrenox.com/api/sub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({pool:'lynchmark',email:document.getElementById('email').value,token:token()||''})});
const d=await r.json();
if(d.ok){msg.textContent='✓ Subscribed!';msg.className='text-center text-sm text-green-600';}
else{msg.textContent=d.error||'Error';msg.className='text-center text-sm text-red-600';}
else{msg.textContent=d.error==='captcha failed'?'Couldn\'t verify you\'re human, try again':d.error||'Error';msg.className='text-center text-sm text-red-600';}
}catch{msg.textContent='Network error';msg.className='text-center text-sm text-red-600';}
window.turnstile?.reset();
};
</script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</body>
</html>
-30
View File
@@ -1,30 +0,0 @@
export async function onRequestPost({ request, env }) {
const { email } = await request.json().catch(() => ({}));
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
return new Response('{"error":"Invalid email"}', { status: 400, headers: { 'Content-Type': 'application/json' } });
const c = request.cf || {};
const geo = [c.city, c.region, c.country].filter(Boolean).join(', ') || 'Unknown';
const res = await fetch('https://api.resend.com/contacts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.RESEND_TOKEN}`
},
body: JSON.stringify({ email })
});
if (!res.ok) {
const text = await res.text();
return new Response(`{"error":"Resend error: ${text}"}`, { status: 500, headers: { 'Content-Type': 'application/json' } });
}
await fetch(env.NTFY_URL, {
method: 'POST',
headers: { Priority: '3', Title: `📬 ${geo}` },
body: email
});
return new Response('{"ok":true}', { headers: { 'Content-Type': 'application/json' } });
}
-27
View File
@@ -1,27 +0,0 @@
export async function onRequestPost({ request, env }) {
const { email } = await request.json().catch(() => ({}));
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
return new Response('{"error":"Invalid email"}', { status: 400, headers: { 'Content-Type': 'application/json' } });
const res = await fetch('https://api.resend.com/contacts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.RESEND_TOKEN}`
},
body: JSON.stringify({ email, unsubscribed: true })
});
if (!res.ok) {
const text = await res.text();
return new Response(`{"error":"Resend error: ${text}"}`, { status: 500, headers: { 'Content-Type': 'application/json' } });
}
await fetch(env.NTFY_URL, {
method: 'POST',
headers: { Priority: '3', Title: `🔕 Unsubscribe` },
body: email
});
return new Response('{"ok":true}', { headers: { 'Content-Type': 'application/json' } });
}
+1 -1
View File
@@ -41,7 +41,7 @@
msg.textContent='Processing...';
msg.classList.remove('hidden');
try{
const r=await fetch('/api/unsubscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:document.getElementById('email').value})});
const r=await fetch('https://newsletter.planetrenox.com/api/unsub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({pool:'lynchmark',email:document.getElementById('email').value})});
const d=await r.json();
if(d.ok){msg.textContent='✓ Unsubscribed';msg.className='text-center text-sm text-green-600';}
else{msg.textContent=d.error||'Error';msg.className='text-center text-sm text-red-600';}