Python不使用SMTP库发送Gmail时遇STARTTLS命令前置错误的解决方法
解决直接用Socket发送Gmail邮件时的"Must issue a STARTTLS command first"错误
嘿,我懂你现在的情况——放弃现成的smtplib库,直接用socket手写SMTP协议,结果碰到Gmail要求先执行STARTTLS命令的报错对吧?别慌,咱们手动把TLS加密的步骤加上就行,毕竟Gmail强制要求加密连接才能发送邮件,咱们一步步来解决:
为什么会出现这个错误?
Gmail的SMTP服务器(smtp.gmail.com,端口587)默认是明文连接,但它会要求你先发送STARTTLS命令,把当前的明文连接升级为TLS加密连接,之后才能进行身份认证和发邮件操作。你之前的代码跳过了这个加密升级步骤,所以服务器直接报错了。
手动实现STARTTLS的步骤
咱们需要在原代码基础上,加入TLS握手的流程,具体步骤如下:
- 连接到Gmail的SMTP 587端口(这是STARTTLS的标准端口)
- 发送
EHLO命令,确认服务器支持STARTTLS扩展 - 发送
STARTTLS命令,等待服务器回复就绪 - 将普通socket升级为TLS加密socket
- 重新发送
EHLO命令(加密连接建立后需要重新初始化会话) - 进行身份认证(Gmail需要base64编码的账号信息)
- 执行后续的发邮件命令(
MAIL FROM、RCPT TO、DATA等)
修改后的完整代码
from socket import * import base64 import ssl # 配置你的邮件信息 mail_server = 'smtp.gmail.com' mail_port = 587 sender_email = '你的gmail邮箱@gmail.com' # 注意:如果账号开了两步验证,必须用Google账号设置里生成的App密码,不是原密码 sender_password = '你的密码或App密码' recipient_email = '收件人邮箱@example.com' # 1. 建立初始socket连接 client_socket = socket(AF_INET, SOCK_STREAM) client_socket.connect((mail_server, mail_port)) # 读取服务器初始响应 recv = client_socket.recv(1024).decode() print(f"服务器初始响应: {recv}") if recv[:3] != '220': print('错误:未收到服务器的220响应') # 2. 发送EHLO命令 helo_command = f'EHLO {gethostname()}\r\n' client_socket.send(helo_command.encode()) recv1 = client_socket.recv(1024).decode() print(f"EHLO响应: {recv1}") if recv1[:3] != '250': print('错误:未收到服务器的250响应') # 3. 发送STARTTLS命令,请求升级加密连接 starttls_command = 'STARTTLS\r\n' client_socket.send(starttls_command.encode()) recv2 = client_socket.recv(1024).decode() print(f"STARTTLS响应: {recv2}") if recv2[:3] != '220': print('错误:未收到服务器的220响应(STARTTLS就绪)') # 4. 将普通socket升级为TLS加密socket client_socket = ssl.wrap_socket( client_socket, ssl_version=ssl.PROTOCOL_TLS_CLIENT, cert_reqs=ssl.CERT_NONE ) # 5. 重新发送EHLO命令(加密连接后必须重新初始化会话) client_socket.send(helo_command.encode()) recv3 = client_socket.recv(1024).decode() print(f"加密后EHLO响应: {recv3}") if recv3[:3] != '250': print('错误:加密后未收到服务器的250响应') # 6. 身份认证(AUTH LOGIN方式) auth_command = 'AUTH LOGIN\r\n' client_socket.send(auth_command.encode()) recv4 = client_socket.recv(1024).decode() print(f"AUTH LOGIN响应: {recv4}") if recv4[:3] != '334': print('错误:未收到服务器的334响应(等待用户名)') # 发送base64编码的用户名 encoded_username = base64.b64encode(sender_email.encode()).decode() + '\r\n' client_socket.send(encoded_username.encode()) recv5 = client_socket.recv(1024).decode() print(f"用户名响应: {recv5}") if recv5[:3] != '334': print('错误:未收到服务器的334响应(等待密码)') # 发送base64编码的密码 encoded_password = base64.b64encode(sender_password.encode()).decode() + '\r\n' client_socket.send(encoded_password.encode()) recv6 = client_socket.recv(1024).decode() print(f"密码响应: {recv6}") if recv6[:3] == '535': print('认证失败!请检查邮箱和密码(如果开了两步验证,必须用App密码)') client_socket.close() exit() if recv6[:3] != '235': print('错误:未收到服务器的235响应(认证成功)') # 7. 发送邮件相关命令 # MAIL FROM mail_from_command = f'MAIL FROM:<{sender_email}>\r\n' client_socket.send(mail_from_command.encode()) recv7 = client_socket.recv(1024).decode() print(f"MAIL FROM响应: {recv7}") if recv7[:3] != '250': print('错误:未收到服务器的250响应') # RCPT TO rcpt_to_command = f'RCPT TO:<{recipient_email}>\r\n' client_socket.send(rcpt_to_command.encode()) recv8 = client_socket.recv(1024).decode() print(f"RCPT TO响应: {recv8}") if recv8[:3] != '250': print('错误:未收到服务器的250响应') # DATA data_command = 'DATA\r\n' client_socket.send(data_command.encode()) recv9 = client_socket.recv(1024).decode() print(f"DATA响应: {recv9}") if recv9[:3] != '354': print('错误:未收到服务器的354响应(允许发送内容)') # 发送邮件内容(注意要包含Subject头,换行用\r\n) msg = f'Subject: 来自Socket的测试邮件\r\n\r\nI love computer networks!\r\n' endmsg = '\r\n.\r\n' client_socket.send(msg.encode()) client_socket.send(endmsg.encode()) recv10 = client_socket.recv(1024).decode() print(f"邮件发送响应: {recv10}") if recv10[:3] != '250': print('错误:未收到服务器的250响应(邮件发送成功)') # 8. 退出连接 quit_command = 'QUIT\r\n' client_socket.send(quit_command.encode()) recv11 = client_socket.recv(1024).decode() print(f"QUIT响应: {recv11}") if recv11[:3] != '221': print('错误:未收到服务器的221响应') client_socket.close()
关键注意事项
- 端口选择:一定要用587端口,465是直接SSL加密的端口,和STARTTLS的流程不一样。
- App密码:如果你的Gmail账号开启了两步验证,必须去Google账号的「安全」设置里生成App密码,用这个密码代替原密码进行认证,否则会提示认证失败。
- TLS版本:代码里用
PROTOCOL_TLS_CLIENT会自动选择合适的TLS版本,避免版本兼容问题。 - 邮件格式:SMTP协议要求换行必须用
\r\n(CRLF),而且邮件内容最好加上Subject头,否则会被识别为无主题邮件。
内容的提问来源于stack exchange,提问作者finites
相关产品推荐
相关产品推荐

