Replace Resend with self-hosted email relay for all email sending
- cryptidAuth.ts: sendEmail() now calls email-relay.jeffemmett.com instead of api.resend.com - boardPermissions.ts: admin request emails use email relay - types.ts: RESEND_API_KEY → EMAIL_RELAY_URL + EMAIL_RELAY_API_KEY - wrangler.toml: updated secrets documentation - Tests updated with new mock env vars Email relay is a lightweight Flask service on Netcup that accepts HTTP POST and sends via Mailcow SMTP. Needed because CF Workers can't do TCP/SMTP directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dea24bde81
commit
1d95d1f398
|
|
@ -75,7 +75,8 @@ function createMockEnv(overrides: Partial<Environment> = {}): Environment {
|
|||
DAILY_API_KEY: 'mock-daily-key',
|
||||
DAILY_DOMAIN: 'mock.daily.co',
|
||||
CRYPTID_DB: createMockD1() as unknown as D1Database,
|
||||
RESEND_API_KEY: 'mock-resend-key',
|
||||
EMAIL_RELAY_URL: 'https://email-relay.jeffemmett.com',
|
||||
EMAIL_RELAY_API_KEY: 'mock-relay-key',
|
||||
APP_URL: 'https://test.example.com',
|
||||
...overrides,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ function createMockEnv(overrides: Partial<Environment> = {}): Environment {
|
|||
DAILY_API_KEY: 'mock-daily-key',
|
||||
DAILY_DOMAIN: 'mock.daily.co',
|
||||
CRYPTID_DB: createMockD1() as unknown as D1Database,
|
||||
RESEND_API_KEY: 'mock-resend-key',
|
||||
EMAIL_RELAY_URL: 'https://email-relay.jeffemmett.com',
|
||||
EMAIL_RELAY_API_KEY: 'mock-relay-key',
|
||||
APP_URL: 'https://test.example.com',
|
||||
...overrides,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1099,13 +1099,13 @@ export async function handleRequestAdminAccess(
|
|||
const body = await request.json().catch(() => ({})) as { reason?: string };
|
||||
|
||||
// Send email to global admin (jeffemmett@gmail.com)
|
||||
if (env.RESEND_API_KEY) {
|
||||
if (env.EMAIL_RELAY_URL && env.EMAIL_RELAY_API_KEY) {
|
||||
const emailFrom = env.CRYPTID_EMAIL_FROM || 'Canvas <noreply@jeffemmett.com>';
|
||||
|
||||
const emailResponse = await fetch('https://api.resend.com/emails', {
|
||||
const emailResponse = await fetch(`${env.EMAIL_RELAY_URL}/send`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
|
||||
'Authorization': `Bearer ${env.EMAIL_RELAY_API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ function generateUUID(): string {
|
|||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
// Send email via Resend
|
||||
// Send email via self-hosted email relay (Mailcow SMTP)
|
||||
async function sendEmail(
|
||||
env: Environment,
|
||||
to: string,
|
||||
|
|
@ -20,15 +20,17 @@ async function sendEmail(
|
|||
htmlContent: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
if (!env.RESEND_API_KEY) {
|
||||
console.error('RESEND_API_KEY not configured');
|
||||
const relayUrl = env.EMAIL_RELAY_URL;
|
||||
const relayKey = env.EMAIL_RELAY_API_KEY;
|
||||
if (!relayUrl || !relayKey) {
|
||||
console.error('EMAIL_RELAY_URL or EMAIL_RELAY_API_KEY not configured');
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch('https://api.resend.com/emails', {
|
||||
const response = await fetch(`${relayUrl}/send`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
|
||||
'Authorization': `Bearer ${relayKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
|
|
@ -41,7 +43,7 @@ async function sendEmail(
|
|||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Resend error:', errorText);
|
||||
console.error('Email relay error:', errorText);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ export interface Environment {
|
|||
DAILY_DOMAIN: string;
|
||||
// CryptID auth bindings
|
||||
CRYPTID_DB?: D1Database;
|
||||
RESEND_API_KEY?: string;
|
||||
EMAIL_RELAY_URL?: string;
|
||||
EMAIL_RELAY_API_KEY?: string;
|
||||
CRYPTID_EMAIL_FROM?: string;
|
||||
APP_URL?: string;
|
||||
// Admin secret for protected endpoints
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ crons = ["0 0 * * *"] # Run at midnight UTC every day
|
|||
# - CLOUDFLARE_API_TOKEN
|
||||
# - FAL_API_KEY # For fal.ai image/video generation proxy
|
||||
# - RUNPOD_API_KEY # For RunPod AI endpoints proxy
|
||||
# - RESEND_API_KEY # For email sending
|
||||
# - EMAIL_RELAY_API_KEY # For email sending via self-hosted relay
|
||||
# - ADMIN_SECRET # For admin-only endpoints
|
||||
#
|
||||
# To set secrets:
|
||||
|
|
|
|||
Loading…
Reference in New Issue