The following steps, scripts and code modifications will create an email notification to a desired email address for BBS events. I have also added a “heartbeat” routine that will generate an email every six hours to me saying the BBS is alive.
With my computer setup and operating this mod, it adds about 10 seconds to the logoff → BBS Ready for call process.
Requirements: linux box with python3 installed, to include smtplib
Window gurus, feel free to post reply on how to make this compatible with Windows!
Upfront note: Color 64 cannot send encrypted traffic, so username and password are exposed in transmissions. I recommend using this on internal networked computer only that does not have the assigned port (in my example below, 8502) exposed to outside world. Otherwise, you open up a potential email resource for those hackers out there!
- Create python script for email server - for this example, we will name it bbsnotify.py
import socket
import smtplib
# Configuration
HOST = "0.0.0.0" # Listen on all interfaces
PORT = 8502 # Telnet port
USERNAME = "*enter your username here*" # Example username
PASSWORD = "enter your password here" # Example password
SMTP_SERVER = "enter your SMTP server"
SMTP_PORT = 587
EMAIL_FROM = "enter your email address here"
EMAIL_TO = "enter desired recipient email here"
EMAIL_SUBJECT = "BBS Event Notification"
EMAIL_PASSWORD = "enter your SMTP password here" # Use environment variables in production
TIMEOUT_SECONDS = 10 # Timeout for user input
def send_email(event_message):
"""Send an email notification with multi-line event message."""
try:
print("Connecting to SMTP server...") # Debug log
# Create an email message with headers
msg = EmailMessage()
msg["Subject"] = EMAIL_SUBJECT
msg["From"] = EMAIL_FROM
msg["To"] = EMAIL_TO
msg["Reply-To"] = "mike.newkirk@gmail.com" # Set custom Reply-To
msg.set_content(event_message) # Allow multi-line content
# Connect to SMTP
server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT, timeout=10)
server.starttls()
server.login(EMAIL_FROM, EMAIL_PASSWORD)
server.send_message(msg) # Send the email using the email.message object
server.quit()
print("Email successfully sent!") # Debug log
return True
except Exception as e:
print(f"Email error: {e}")
return str(e) # Return error message
def receive_multiline(client_socket):
"""Receive multiple lines of input until 'END' is entered or timeout occurs."""
client_socket.send(b"Enter Event (type 'END' on a new line to finish):\r\n")
lines = []
client_socket.settimeout(TIMEOUT_SECONDS) # Set timeout
try:
while True:
line = receive_line(client_socket)
if line is None:
break # Timeout or client disconnected
if line.strip().upper() == "END":
return "\n".join(lines) # Preserve newlines in email body
lines.append(line)
except socket.timeout:
client_socket.send(b"\r\nTimeout reached. Connection closing.\r\n")
print("Timeout reached. Closing connection.")
return None # Indicate timeout occurred
return "\n".join(lines)
def receive_line(client_socket):
"""Receive a full line from the client, handling Telnet input properly."""
data = b""
while True:
try:
chunk = client_socket.recv(1)
if not chunk:
return None # Client disconnected
if chunk in [b"\n", b"\r"]: # End of input line
break
data += chunk
except socket.timeout:
return None # Timeout reached
return data.decode(errors='ignore').strip()
def handle_client(client_socket):
"""Handle the Telnet connection."""
try:
client_socket.send(b"Username: ")
username = receive_line(client_socket)
print(f"Received username: {username}") # Debug log
client_socket.send(b"Password: ")
password = receive_line(client_socket)
print(f"Received password: {password}") # Debug log
if username == USERNAME and password == PASSWORD:
event_message = receive_multiline(client_socket) # Allow multi-line input
if event_message:
print(f"Received event:\n{event_message}") # Debug log
result = send_email(event_message)
if result is True:
client_socket.send(b"Event notification sent.\r\n")
else:
error_msg = f"Failed to send notification: {result}\r\n"
client_socket.send(error_msg.encode()) # Send SMTP error message
else:
client_socket.send(b"No event entered. Closing connection.\r\n")
else:
client_socket.send(b"Authentication failed.\r\n")
except Exception as e:
print(f"Error handling client: {e}")
finally:
print("Closing connection") # Debug log
client_socket.close()
def start_server():
"""Start the Telnet server."""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((HOST, PORT))
server.listen(5)
print(f"Server listening on port {PORT}...")
while True:
try:
client_socket, addr = server.accept()
print(f"Connection from {addr}")
handle_client(client_socket)
except KeyboardInterrupt:
print("\nServer shutting down.")
server.close()
break
if __name__ == "__main__":
start_server()
#Instructions:
#Modify the USERNAME, PASSWORD, SMTP_SERVER, and email details.
#Start the script: python3 bbsnotify.py
#Connect via Telnet: telnet <your_server_ip> 8502
#Enter the username, password, and event.
#Ensure "end" is last transmission alone on a line.
#The script will send the event via email.
- Determine the IP of your linux computer running the script for reference
- Run the script at your linux computer
- At the Commodore 64, edit and save √bbs.init with the following modifications:
35700 rem email notification
35702 .21:!12.:'“+++”:gosub35725
35705 ‘“ATDT your-internal-ip-address-here:8502”:gosub35725:’“your-username”:gosub35725
35706 '“your-password”:gosub35725:gosub1110:'da$+" “+t$+” "+cr$+em$+cr$+“end”+cr$:return
35725 forxx=1to500:nextxx:return
The loop at 35725 will give enough time for responses from the linux email script, though it can probably be shortened some, but this worked fine for my system.
EM$ is obviously what is being used to record specific events. This must also be cleared out at some point after each transmission. Below are my placements and use for EM$:
- Clearing of EM string:
Line 12014 where √wfc is being loaded, add EM$=“” here
12014 !17,.!18,.:!16,1:em$=“”:f$=“√wfc”:gosub205
My selected events:
- Modify line 12185 to reflect a logon
12185 ifpw$=i$then#“Loading parameters…”:em$=“logon ok”:goto12300
- Add line 12486
12486 em$=em$+cr$+@21(na$)+" entered system"
- modify line 18200 for new user
18200 nu=nu+1:em$=em$+cr$+"new user! "+@21(na$)
- Modify line 12650 to report a system shutdown
12650 em$=em$+cr$+“system shutdown ordered!”:gosub35700:.05:goto9999
- Finally, add a post-logoff event to run the routine:
19046 gosub35700
What it does
The system will track events in EM$ wherever you put it. Just remember to do “EM$=EM$+CR$+”" wherever you want something recorded. Because we are using PETSCII - something the linux server is not - be sure not to use capital letters; keep everything lower case. If using a username, convert username using @21 function: @21(na$)
To add heartbeat “BBS is alive!” routine:
- 1st, add these following lines to √bbs.init
12051 ifhb=1then12054
12052 ifleft$(t$,4)="6:00"orleft$(t$,5)="12:00"thenhb=1:em$=“heart”:gosub35700
12054 ifleft$(t$,4)="6:02"orleft$(t$,5)="12:02"thenhb=0
- Next, in the same file, modify your routine at 35700 by adding this line (35701):
35701 ifem$="heart"thenem$=“heartbeat!”+cr$+“my bbs is alive!”
You can place other events in √bbs.msgs and √bbs.xfers in the same manner using em$. The handling routine for email only needs to be in √bbs.init. I have alerts set for user uploads and sysop feedback submitted.
With the script running, the commodore at post-logoff will telnet to your linux box and enter the details for the email to be sent; after which, it will disconnect and clear EM$, then go back to the wait-for-caller screen. With a little tweaking of the email script, this can be expanded to send notifications to users! As a Bonus for your efforts, my Game Mods I have on this website already store events for this feature, so you will see what games are being utilized and by whom.
This is the observation at the linux box:
I use Brevo for my SMTP service - they are great. The only thing I noticed is emails were going straight to junk folder, so I applied a rule in my outlook to push those emails into a specific folder and that resolved the issue.