首页/新闻资讯/正文详情

Codex 实战 Skills:用 HTML 模板自动填充客户名称并发送带附件报表的邮件技能

发布时间:2026/9/25 14:53:33 来源:云帆数科 栏目:资讯中心
Codex 实战 Skills:用 HTML 模板自动填充客户名称并发送带附件报表的邮件技能
1. 客户报表邮件为什么总在最后一步翻车做 B2B 运营的朋友大概率都经历过这种场景月底要给几十上百个客户发月度报表HTML 模板早就设计好了数据也跑出来了附件 Excel 也生成了结果卡在“把客户名称填进去、把附件挂上去、点发送”这个环节。手动操作的话一封封改称呼、一封封传附件发到第十封就开始怀疑人生发到第三十封必然出现张冠李戴——把 A 公司的报表发给了 B 公司的联系人。这个问题的本质不是“发邮件”难而是个性化内容 附件 批量发送这三件事叠在一起之后人工操作的出错概率呈指数上升。你需要的是一个可复用的技能Skill输入客户列表和报表文件输出一批已经发出去的、称呼正确、附件正确的邮件。Codex Skills 的价值就在这里。它不是一个孤立的 API 调用而是一段可以被 Agent 反复调用的封装逻辑。你可以把它理解成一个“邮件工厂”原料是 HTML 模板和客户数据产线是模板渲染和 MIME 组装出货口是 SMTP 发送。本文会给出一个可以直接复制运行的 Skill 配置骨架包含模板占位符设计、附件路径处理、收件人字段映射以及一次本地验证动作帮你把从模板到发信的完整链路跑通。适合谁看需要定期给客户发报表的运营开发、做 CRM 自动化的后端同学、以及想把重复劳动封装成 Skill 的 Agent 开发者。下面所有代码和配置都可以在本地直接跑不需要复杂的部署环境。2. 前置准备用 TaoToken 拿到可调用的模型能力在写邮件 Skill 之前先解决“谁来驱动这个 Skill”的问题。如果你打算让 Codex 或类似的编码 Agent 帮你生成、调试、迭代这段邮件发送逻辑你需要一个稳定的模型调用入口。我试过用 TaoToken 来统一管理这类调用它的定位是给开发者和 Agent 工作流提供模型接入能力官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。具体操作分三步。第一步打开控制台创建 API Key地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面生成一个密钥复制保存好后面配置环境变量要用。第二步如果你只是想让模型帮你审查邮件模板的 HTML 结构或者生成 Jinja2 占位符可以直接用模型对话页面 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 快速验证。第三步如果你打算长期用 Codex 做编码和 Agent 任务建议看一下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它更适合高频的代码生成和调试场景。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有完整的鉴权和请求示例。如果你用的是 Claude Code 这类工具可以参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite 的配置说明。把 Key 配到环境变量里后面 Python 脚本直接读取export TAOTOKEN_API_KEY你的密钥 export SMTP_HOSTsmtp.example.com export SMTP_PORT587 export SENDER_EMAILreportyourcompany.com export SENDER_PASSWORD你的邮箱授权码注意SMTP 密码不要硬编码在代码里用环境变量或密钥管理服务。邮箱授权码和登录密码通常不是同一个东西去邮箱设置里单独生成。3. 可复制的 Skill 配置骨架模板占位符 附件路径 收件人字段这一节是核心。我会把整个 Skill 拆成三个可独立替换的模块HTML 模板、客户数据映射、发送逻辑。你只需要改模板里的占位符和 CSV 的列名就能套用到自己的业务场景。3.1 HTML 模板用占位符标记客户名称和报表字段邮件客户端的 CSS 支持很有限别用 Flexbox 和 Grid老老实实用 table 布局样式尽量内联。下面是一个最小可用的报表邮件模板保存为email_template.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 title月度业务报告/title /head body stylefont-family: Arial, sans-serif; background:#f4f4f4; margin:0; padding:0; div stylemax-width:600px; margin:20px auto; background:#ffffff; border-radius:8px; overflow:hidden; div stylebackground:#2c3e50; color:#ffffff; padding:20px; text-align:center; h1 stylemargin:0; font-size:20px;{{ company_name }} 月度业务简报/h1 /div div stylepadding:20px; color:#333333; line-height:1.6; p尊敬的 strong{{ customer_name }}/strong/p p您好以下是您在 {{ month }} 的业务数据汇总/p table stylewidth:100%; border-collapse:collapse; margin-top:15px; tr th styleborder:1px solid #ddd; padding:8px; background:#f2f2f2;指标/th th styleborder:1px solid #ddd; padding:8px; background:#f2f2f2;数值/th /tr tr td styleborder:1px solid #ddd; padding:8px;总销售额/td td styleborder:1px solid #ddd; padding:8px;¥ {{ total_sales }}/td /tr tr td styleborder:1px solid #ddd; padding:8px;订单数量/td td styleborder:1px solid #ddd; padding:8px;{{ order_count }}/td /tr /table p详细报表请查阅随信附件。/p p祝商祺br{{ sender_name }}/p /div div stylebackground:#eeeeee; padding:15px; text-align:center; font-size:12px; color:#777; 此邮件由系统自动发送请勿直接回复。 /div /div /body /html模板里的{{ customer_name }}、{{ company_name }}、{{ total_sales }}就是占位符Jinja2 渲染时会替换成客户数据里的实际值。注意{{ customer_name }}是客户联系人姓名{{ company_name }}是客户公司名这两个别搞混否则邮件开头会变成“尊敬的某某科技有限公司”读起来很怪。3.2 客户数据映射CSV 列名与模板变量的对应关系准备一个customers.csv列名和模板变量一一对应customer_name,company_name,email,total_sales,order_count,month 李明,创新科技有限公司,limingexample.com,150000.00,12,2024-06 王芳,未来贸易集团,wangfangexample.com,89000.50,8,2024-06 张伟,恒达物流,zhangweiexample.com,230000.00,25,2024-06这里的关键是email列它决定了收件人字段。发送逻辑里会读取这一列作为To地址。附件路径不放在 CSV 里而是作为 Skill 的全局参数传入因为同一批客户的报表文件通常是同一个或者按客户 ID 动态生成。3.3 发送逻辑渲染模板、挂载附件、调用 SMTP下面是完整的 Python Skill 骨架保存为email_skill.py。代码里包含了模板渲染、附件挂载、SMTP 发送和错误处理import os import csv import smtplib import logging from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.base import MIMEBase from email import encoders from jinja2 import Environment, FileSystemLoader from datetime import datetime logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[logging.FileHandler(email_skill.log, encodingutf-8), logging.StreamHandler()] ) logger logging.getLogger(__name__) class ReportEmailSkill: def __init__(self, smtp_host, smtp_port, sender_email, sender_password, template_dir): self.smtp_host smtp_host self.smtp_port smtp_port self.sender_email sender_email self.sender_password sender_password self.jinja_env Environment(loaderFileSystemLoader(template_dir), autoescapeTrue) def render_template(self, template_name, context): template self.jinja_env.get_template(template_name) return template.render(**context) def build_message(self, to_email, subject, html_content, attachment_pathNone): msg MIMEMultipart() msg[From] self.sender_email msg[To] to_email msg[Subject] subject msg.attach(MIMEText(html_content, html, utf-8)) if attachment_path and os.path.exists(attachment_path): with open(attachment_path, rb) as f: part MIMEBase(application, octet-stream) part.set_payload(f.read()) encoders.encode_base64(part) filename os.path.basename(attachment_path) part.add_header(Content-Disposition, fattachment; filename{filename}) msg.attach(part) logger.info(f附件已挂载: {filename}) return msg def send_one(self, to_email, subject, html_content, attachment_pathNone): try: msg self.build_message(to_email, subject, html_content, attachment_path) server smtplib.SMTP(self.smtp_host, self.smtp_port) server.ehlo() server.starttls() server.ehlo() server.login(self.sender_email, self.sender_password) server.sendmail(self.sender_email, [to_email], msg.as_string()) server.quit() logger.info(f发送成功: {to_email}) return True except smtplib.SMTPAuthenticationError: logger.error(f认证失败检查邮箱授权码: {to_email}) return False except Exception as e: logger.error(f发送异常: {e}, 收件人: {to_email}) return False def run_bulk(self, csv_path, template_name, attachment_pathNone): success, failed 0, 0 with open(csv_path, newline, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: context { customer_name: row.get(customer_name, 客户), company_name: row.get(company_name, ), total_sales: row.get(total_sales, 0), order_count: row.get(order_count, 0), month: row.get(month, datetime.now().strftime(%Y-%m)), sender_name: 客户成功团队 } html_content self.render_template(template_name, context) subject f{context[company_name]} - {context[month]} 业务报告 ok self.send_one(row[email], subject, html_content, attachment_path) if ok: success 1 else: failed 1 logger.info(f批量完成。成功: {success}, 失败: {failed}) return {success: success, failed: failed} if __name__ __main__: skill ReportEmailSkill( smtp_hostos.getenv(SMTP_HOST), smtp_portint(os.getenv(SMTP_PORT, 587)), sender_emailos.getenv(SENDER_EMAIL), sender_passwordos.getenv(SENDER_PASSWORD), template_dir. ) result skill.run_bulk( csv_pathcustomers.csv, template_nameemail_template.html, attachment_pathmonthly_report.xlsx ) print(result)这段代码里几个容易踩坑的地方我单独说一下。autoescapeTrue是防止客户名称里带特殊字符导致 HTML 结构错乱比如客户公司名里有个符号不开转义的话邮件正文可能直接崩掉。附件文件名用双引号包起来filename{filename}有些邮件客户端对中文文件名处理不好建议附件名用英文加日期比如report_202406.xlsx。starttls()之后要再调一次ehlo()这是 SMTP 协议的要求漏掉的话某些服务器会拒绝登录。4. 本地验证跑一次发送并确认结果代码写好了别急着批量发。先做一次本地验证确认模板渲染、附件挂载、SMTP 连接三个环节都正常。第一步把customers.csv里只留一行收件人改成你自己的邮箱。这样即使出问题也不会骚扰到客户。第二步准备一个测试附件随便建一个文件echo test report content monthly_report.xlsx第三步运行脚本python email_skill.py如果一切正常你会在终端看到类似输出2024-06-15 10:30:01 - INFO - 附件已挂载: monthly_report.xlsx 2024-06-15 10:30:03 - INFO - 发送成功: your_emailexample.com 2024-06-15 10:30:03 - INFO - 批量完成。成功: 1, 失败: 0 {success: 1, failed: 0}然后去邮箱里检查三件事邮件正文里的客户名称是否替换成了 CSV 里的值附件是否正常显示且能打开邮件主题是否包含了公司名和月份。这三项都对了再把 CSV 恢复成完整客户列表把附件换成真实报表文件正式跑批量。如果你想在发送前先预览渲染后的 HTML可以在run_bulk里加一行print(html_content)或者把渲染结果写到一个.html文件里用浏览器打开看效果。这个习惯能帮你提前发现模板里的样式问题比发出去之后再后悔强。5. 本篇常见错排查5.1 报错 SMTPAuthenticationError: 535这是最常见的错误原因通常是用了邮箱登录密码而不是授权码。QQ 邮箱、163 邮箱、Gmail 都需要在设置里单独开启 SMTP 服务并生成授权码。另外检查SENDER_EMAIL是否和登录账号一致有些企业邮箱要求发件人地址必须和认证账号相同。5.2 附件收到后变成 .bin 文件或无法打开检查MIMEBase的第二个参数。上面代码用的是application, octet-stream这是通用二进制流。如果你发的是 Excel可以改成application, vnd.openxmlformats-officedocument.spreadsheetml.sheet邮件客户端识别率更高。但即使不改大多数客户端也能根据文件名后缀正确识别。5.3 邮件正文里客户名称显示为空白说明 Jinja2 渲染时customer_name变量没取到值。检查 CSV 的列名是否和代码里row.get(customer_name)一致注意大小写和空格。CSV 文件保存时用 UTF-8 编码Excel 另存为 CSV 有时会变成 GBK导致中文列名乱码。5.4 发送成功但收件人没收到先查垃圾邮件文件夹。如果批量发送频率太高邮件服务商会限流。建议每发 20 封暂停 5 秒在循环里加time.sleep(5)。另外确认发件人域名是否配置了 SPF 记录没有 SPF 的域名发出的邮件很容易被判定为垃圾邮件。5.5 模板渲染报 UndefinedErrorJinja2 默认对未定义的变量会报错。如果你在模板里用了{{ growth_rate }}但 CSV 里没有这一列就会触发。解决办法是在context字典里给所有模板变量提供默认值或者用{{ growth_rate | default(0) }}过滤器。6. 把 Skill 接进你的 Agent 工作流邮件发送跑通之后下一步是让它变成 Agent 可以自动调用的技能。你可以把ReportEmailSkill封装成一个函数注册到 Codex 的工具列表里这样当 Agent 判断“需要给客户发报表”时就能自动传入 CSV 路径和附件路径完成整个链路。如果你在调试 Agent 调用逻辑时需要快速验证模型输出可以用模型对话页面 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 做交互测试。长期做编码和 Agent 开发的话Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 的额度更适合高频调用。API Key 在控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 管理接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后分享一个实用技巧把每次发送的success和failed计数写到一个日志文件里按日期归档。这样月底对账时能快速定位哪些客户没收到报表比翻邮箱发件箱高效得多。邮件 Skill 的价值不在于“能发”而在于“发得准、可追溯、能复用”。

相关推荐

数独生成算法全解析:终盘构造、挖洞策略与唯一解验证实战
数独生成算法全解析:终盘构造、挖洞策略与唯一解验证实战

1. 先分清“终盘”和“题目”:生成算法的设计边界先说个大多数第一次写数独生成器的人都会踩的坑:上来就想着“随机往格子里填数字,填满一个99还满足规则就算大功告成”。你要是真这么做了,会发现程序跑上半天一个结果都出不来&am… · 2026/9/25 14:53:33

ModLens 源码剖析:10 路视觉来源的 Failover 链如何做到“永不沉默“的故障转移
ModLens 源码剖析:10 路视觉来源的 Failover 链如何做到“永不沉默“的故障转移

ModLens 源码剖析:10 路视觉来源的 Failover 链如何做到"永不沉默"的故障转移 【免费下载链接】modlens The first vision plugin for DeepSeek Harness, and the vision bridge for every text-only coding agent. Paste an image, get structured JSON … · 2026/9/25 14:53:27

SpringBoot+Vue体育馆预约平台系统开发实战:从数据库设计到冲突检测
SpringBoot+Vue体育馆预约平台系统开发实战:从数据库设计到冲突检测

如果你正在做一个“基于SpringBootVue的体育馆使用预约平台管理系统”,大概率是课程设计、毕业设计,或者想作为求职项目放进简历里。这个技术组合——SpringBoot、Vue、MySQL、MyBatis——几乎是国内Java全栈项目最经典的一套,网上模板多到泛… · 2026/9/25 14:53:21

FTP双通道原理与Active/Passive模式实战解析
FTP双通道原理与Active/Passive模式实战解析

1. FTP不是“传文件的软件”,而是一套精密协作的通信协议很多人第一次接触FTP,是在Windows资源管理器里输入ftp://192.168.1.100,或者用FileZilla点几下就传好了照片、文档、设计稿。于是下意识觉得:“FTP不就是个上传下载工具嘛&… · 2026/9/25 15:29:10

Atlas 300V 24G推理加速卡部署YOLO全流程详解
Atlas 300V 24G推理加速卡部署YOLO全流程详解

1. Atlas 300V 24G到底是一张什么卡,凭什么能跑YOLO先说结论:Atlas 300V 24G确实是一块AI运算加速卡,而且是一块专门为推理场景设计的加速卡。很多人第一次看到“300V”这个名字会误以为是显卡,或者以为是某种视频采集卡&#xff… · 2026/9/25 15:28:58

CiLocks钓鱼页面设计解析:仿Instagram特效页背后的社会工程心理学
CiLocks钓鱼页面设计解析:仿Instagram特效页背后的社会工程心理学

CiLocks钓鱼页面设计解析:仿Instagram特效页背后的社会工程心理学 【免费下载链接】CiLocks Crack Interface lockscreen, Metasploit and More Android/IOS Hacking 项目地址: https://gitcode.com/GitHub_Trending/ci/CiLocks CiLocks 是一款面向 Android/… · 2026/9/25 15:28:58

Atlas 300V 24G推理卡部署YOLO:从ONNX到OM全流程解析
Atlas 300V 24G推理卡部署YOLO:从ONNX到OM全流程解析

后台最近被问得最多的两个问题,一个是“atlas 部署 yolo 怎么搞”,另一个是“atlas 300v 24g 是运算加速卡吗”。我一听就知道,问的人多半刚接触昇腾这套东西,手里要么有张卡不知道干啥,要么正准备上视频分析项目。先说… · 2026/9/25 15:28:52

从零搭建AI Agent工具链:CLI、MCP与OpenRouter实战指南
从零搭建AI Agent工具链:CLI、MCP与OpenRouter实战指南

1. 从"treg"这个模糊词说起:它到底指什么第一次看到"treg"这三个字母,我脑子里蹦出来的第一反应是生物学里的调节性T细胞(Regulatory T cell,缩写Treg)。但结合后面跟着的一串热词——OpenRouter、… · 2026/9/25 15:28:39

当ChatBI进入企业,如何守住数据底线?零数据保留策略的边界与落地
当ChatBI进入企业,如何守住数据底线?零数据保留策略的边界与落地

导语 不少企业接入ChatBI(基于大模型的智能对话式BI,可让业务用自然语言直接问数分析)后,一边享受着业务自助分析效率的提升,一边又陷入了数据安全的焦虑:原始业务数据会不会流出去?大模型会不会… · 2026/9/25 15:28:26

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码