Files
fad-trade-next/actions/newsletter.ts
砂糖 b53f104d9b refactor: 更新品牌信息为巨丰钢铁并优化相关配置
更新公司品牌信息从"福安德外贸"到"巨丰钢铁",包括:
1. 修改所有相关文档中的公司名称和描述
2. 更新网站配置和邮件模板
3. 移除不必要的分析工具和社交媒体链接
4. 优化i18n多语言配置
5. 调整next.config.mjs输出模式为standalone
6. 更新favicon和logo图片
7. 清理未使用的代码和文件
2026-01-26 15:40:31 +08:00

125 lines
3.7 KiB
TypeScript

import { normalizeEmail, validateEmail } from '@/lib/email';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
import { headers } from 'next/headers';
import { Resend } from 'resend';
// initialize resend
const resend = new Resend(process.env.RESEND_API_KEY);
// Resend Audience ID
const AUDIENCE_ID = process.env.RESEND_AUDIENCE_ID!;
// initialize Redis
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
const REDIS_RATE_LIMIT_KEY = process.env.UPSTASH_REDIS_NEWSLETTER_RATE_LIMIT_KEY!;
const DAY_MAX_SUBMISSIONS = parseInt(process.env.DAY_MAX_SUBMISSIONS || '10');
// create rate limiter
const limiter = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(DAY_MAX_SUBMISSIONS, '1d'),
prefix: REDIS_RATE_LIMIT_KEY,
});
// Shared rate limit check
async function checkRateLimit() {
const headersList = await headers();
const ip = headersList.get('x-real-ip') ||
headersList.get('x-forwarded-for') ||
'unknown';
const { success } = await limiter.limit(ip);
if (!success) {
throw new Error('Too many submissions, please try again later');
}
}
export async function subscribeToNewsletter(email: string) {
try {
await checkRateLimit();
const normalizedEmail = normalizeEmail(email);
const { isValid, error } = validateEmail(normalizedEmail);
if (!isValid) {
throw new Error(error || 'Invalid email address');
}
// Check if already subscribed
// const list = await resend.contacts.list({ audienceId: AUDIENCE_ID });
// const user = list.data?.data.find((item) => item.email === normalizedEmail);
// if (user) {
// return { success: true, alreadySubscribed: true };
// }
// Add to audience
await resend.contacts.create({
audienceId: AUDIENCE_ID,
email: normalizedEmail,
});
// Send welcome email
const unsubscribeToken = Buffer.from(normalizedEmail).toString('base64');
const unsubscribeLink = `${process.env.NEXT_PUBLIC_SITE_URL}/unsubscribe?token=${unsubscribeToken}`;
await resend.emails.send({
from: 'NextForge <' + process.env.ADMIN_EMAIL! + '>',
to: normalizedEmail,
subject: 'Welcome to 巨丰钢铁',
html: `
<h2>Welcome to 巨丰钢铁</h2>
<p>Thank you for subscribing to the newsletter. You will receive the latest updates and news.</p>
<p style="margin-top: 20px; font-size: 12px; color: #666;">
If you wish to unsubscribe, please <a href="${unsubscribeLink}">click here</a>
</p>
`,
headers: {
"List-Unsubscribe": `<${unsubscribeLink}>`,
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
}
});
return { success: true };
} catch (error) {
console.error('Newsletter subscription failed:', error);
throw error;
}
}
export async function unsubscribeFromNewsletter(token: string) {
try {
await checkRateLimit();
const email = Buffer.from(token, 'base64').toString();
const normalizedEmail = normalizeEmail(email);
const { isValid, error } = validateEmail(normalizedEmail);
if (!isValid) {
throw new Error(error || 'Invalid email address');
}
// Check if subscribed
const list = await resend.contacts.list({ audienceId: AUDIENCE_ID });
const user = list.data?.data.find((item) => item.email === normalizedEmail);
if (!user) {
throw new Error('This email is not subscribed to our notifications');
}
// Remove from audience
await resend.contacts.remove({
audienceId: AUDIENCE_ID,
email: normalizedEmail,
});
return { success: true, email: normalizedEmail };
} catch (error) {
console.error('Newsletter unsubscribe failed:', error);
throw error;
}
}