You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:43:54