如何识别PHP+SendGrid群发邮件中To收件人点击验证按钮?
问题描述
我用PHP结合SendGrid API发送邮件,收件人(To)是单个用户,抄送(Cc)是一组用户,邮件内包含确认按钮并附带token(也可使用JWT token)。但当前token对所有收件人相同,请问怎么确认是To字段的邮箱用户点击了该确认按钮?
以下是我使用的示例代码:
$tokenValue = 'AnyRandomValueorJWTTokenGoesHere'; $mailContent = '<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="style.css"> </head> <body> <a href="localhost:3000/mailVerify.php?myToken='.$tokenValue.'" class="button" > Verify </a> <script src="script.js"></script> </body> </html>'; $email = new \SendGrid\Mail\Mail(); $email->setFrom("test@example.com", "Example User"); $email->setSubject("Sending with Twilio SendGrid is Fun"); $email->addTo("test@example.com", "Example User"); foreach ($cc as $email) { $email->addCc($email, explode("@",$email)[0]); } $email->addContent("text/html", $mailContent); $sendgrid = new \SendGrid(getenv('SENDGRID_API_KEY')); try { $response = $sendgrid->send($email); print $response->statusCode() . "\n"; print_r($response->headers()); print $response->body() . "\n"; } catch (Exception $e) { echo 'Caught exception: '. $e->getMessage() ."\n"; }
解决方案
1. 用JWT嵌入收件人身份信息
最可靠的方式是把To字段的邮箱地址作为JWT的claim(比如sub或自定义字段),验证token时直接提取邮箱,判断是否为目标收件人。
步骤:
- 生成JWT时,将To用户的邮箱加入payload
- 邮件确认链接使用该JWT作为token
- 在验证接口解析JWT并校验邮箱是否匹配目标To地址
修改后的代码示例:
先安装JWT库(以firebase/php-jwt为例):
composer require firebase/php-jwt
生成JWT并发送邮件的代码:
use Firebase\JWT\JWT; $secretKey = 'your_jwt_secret_key'; // 建议存入环境变量,保证安全 $toEmail = "test@example.com"; // To字段的目标邮箱 $payload = [ 'iss' => 'your_app_name', 'sub' => $toEmail, // 将目标邮箱放入JWT的sub字段 'exp' => time() + 3600 * 24, // Token有效期24小时 ]; $tokenValue = JWT::encode($payload, $secretKey, 'HS256'); $mailContent = '<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="style.css"> </head> <body> <a href="localhost:3000/mailVerify.php?myToken='.$tokenValue.'" class="button" > Verify </a> <script src="script.js"></script> </body> </html>'; $email = new \SendGrid\Mail\Mail(); $email->setFrom("test@example.com", "Example User"); $email->setSubject("Sending with Twilio SendGrid is Fun"); $email->addTo($toEmail, "Example User"); foreach ($cc as $ccEmail) { $email->addCc($ccEmail, explode("@",$ccEmail)[0]); } $email->addContent("text/html", $mailContent); $sendgrid = new \SendGrid(getenv('SENDGRID_API_KEY')); try { $response = $sendgrid->send($email); print $response->statusCode() . "\n"; print_r($response->headers()); print $response->body() . "\n"; } catch (Exception $e) { echo 'Caught exception: '. $e->getMessage() ."\n"; }
验证接口mailVerify.php的代码:
use Firebase\JWT\JWT; use Firebase\JWT\Key; $secretKey = 'your_jwt_secret_key'; $targetToEmail = "test@example.com"; // 预存的目标To邮箱 if (!isset($_GET['myToken'])) { die("无效请求"); } try { $decoded = JWT::decode($_GET['myToken'], new Key($secretKey, 'HS256')); $clickedEmail = $decoded->sub; if ($clickedEmail === $targetToEmail) { // 确认是To字段用户点击,执行验证逻辑 echo "验证成功!"; } else { // 非目标用户点击,拒绝操作 echo "您无权限执行此操作"; } } catch (Exception $e) { die("无效或过期的token"); }
2. 给Cc用户隐藏/禁用确认按钮(可选优化)
因为Cc用户无需执行确认操作,可通过SendGrid动态模板给不同收件人展示不同内容:
- 创建SendGrid动态模板,用模板语法判断收件人是否为To用户,仅给To用户显示按钮
- 发送邮件时给每个收件人传入专属标识(比如
is_target_recipient)
示例代码(使用动态模板):
$toEmail = "test@example.com"; $email = new \SendGrid\Mail\Mail(); $email->setFrom("test@example.com", "Example User"); $email->setSubject("Sending with Twilio SendGrid is Fun"); $email->addTo($toEmail, "Example User"); // 给To用户传入标识 $email->addDynamicTemplateData('is_target_recipient', true); $email->addDynamicTemplateData('verify_token', $tokenValue); foreach ($cc as $ccEmail) { $personalization = new \SendGrid\Mail\Personalization(); $personalization->addCc(new \SendGrid\Mail\Cc($ccEmail, explode("@",$ccEmail)[0])); // 给Cc用户传入标识 $personalization->addDynamicTemplateData('is_target_recipient', false); $email->addPersonalization($personalization); } // 设置模板ID $email->setTemplateId('your_sendgrid_template_id'); $sendgrid = new \SendGrid(getenv('SENDGRID_API_KEY')); // ... 发送逻辑和之前一致
动态模板内容示例(Handlebars语法):
{{#if is_target_recipient}} <a href="localhost:3000/mailVerify.php?myToken={{verify_token}}" class="button">Verify</a> {{else}} <p>此邮件为抄送,无需操作</p> {{/if}}
3. 额外校验(补充方案)
如果不想用JWT,可在验证时让用户输入邮箱,对比是否为To字段的邮箱,但这种方式体验较差,仅作为备选。
内容的提问来源于stack exchange,提问作者Shubham Chaudhary
相关产品推荐
相关产品推荐

