4 months ago
My app only receives emails and stores them in a log file
DNS Config
mx @ mail.domain.com. (Priority: 0)
cname mail foo.up.railway.app.
# Use a base image with Python
FROM python:3.9-slim
# Set environment variable to disable output buffering
ENV PYTHONUNBUFFERED=1
# Set the working directory
WORKDIR /app
# Copy the Python script and dependencies into the container
COPY . .
# Install necessary dependencies
RUN pip install -r requirements.txt
# Expose the ports the apps will run on
EXPOSE 25 8000
# Run both the email service (SMTP) and the Flask app (log-server) using gunicorn
CMD ["sh", "-c", "python -u Inbox.py & gunicorn -w 2 -b 0.0.0.0:8000 Log_server:app"]
services:
email-service:
build: .
ports:
- "25:25"
volumes:
- shared-data:/app/shared
log-server:
build: .
ports:
- "8000:8000"
volumes:
- shared-data:/app/shared
volumes:
shared-data:
I've ran docker-compose up locally and both the smtp server and the flask server work just fine
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg.set_content('This is a test email sent from CLI.')
msg['Subject'] = 'Test Email from CLI'
msg['From'] = 'sender@example.com'
msg['To'] = 'receiver@example.com'
smtp = smtplib.SMTP('127.0.0.1', 25)
smtp.send_message(msg)
smtp.quit()
This is the code I used to test the server locally and it worked just fine, but I don't get how I deploy it and how I expose port 25 on mail.domain.com
0 Replies
4 months ago
Railway does not support docker compose, nor does it support running multiple apps within one service
import asyncio
from aiosmtpd.controller import Controller
import logging
# Configure logging
logging.basicConfig(level=logging.INFO, filename="./shared/emails.log", filemode="a",
format="%(asctime)s - %(message)s")
def log_email(email_content):
logging.info("Received email:\n%s", email_content)
class EmailHandler:
async def handle_DATA(self, server, session, envelope):
try:
# Decode email content and log it
email_content = envelope.content.decode('utf-8')
log_email(email_content)
print(f"Email received:\n{email_content}")
except Exception as e:
print(f"Error processing email: {e}")
return '250 Message accepted for delivery'
async def main():
handler = EmailHandler()
controller = Controller(handler, hostname="0.0.0.0", port=25)
print("Starting SMTP server on 0.0.0.0:25")
controller.start()
try:
await asyncio.Event().wait() # Keep the server running
except KeyboardInterrupt:
print("Stopping SMTP server...")
finally:
controller.stop()
if __name__ == "__main__":
asyncio.run(main())
from flask import Flask, Response
import os
# Define the log file path
LOG_FILE_PATH = "./shared/emails.log"
# Initialize the Flask app
app = Flask(__name__)
@app.route('/')
def serve_log_file():
if os.path.exists(LOG_FILE_PATH):
# Open the log file and read its content
with open(LOG_FILE_PATH, 'rb') as log_file:
log_data = log_file.read()
# Return the log file as a plain text response
return Response(log_data, mimetype='text/plain')
else:
# If the log file doesn't exist, return a 404
return "Log file not found.", 404
if __name__ == "__main__":
# Start the Flask server on port 8000
app.run(host='0.0.0.0', port=8000)
4 months ago
To make this work, you'll want to separate out your two apps into separate services and have them communicate through the private network
4 months ago
Docker is fine, not docker compose
4 months ago
Additionally, you may have issues running a mailserver on Railway (at least on non-metal regions) as GCP blocks common mailserver ports and Railway's own proxying may mess with it as well
4 months ago
It may deploy fine, but only one port is properly exposed
4 months ago
These links may help you out
https://railway.app/changelog/2024-04-19-drag-and-drop-docker
https://railway.app/compose
Ok I wanted to get atleast a poc running so I tweaked the code to only log emails and got rid of the flask app but nothing is still going through. I am wondering how I expose port 25 on my domain instead of 443.
4 months ago
by default, Railway exposes the PORT environment variable. if you need to expose port 25, create a PORT environment variable in your service with the value 25
4 months ago
best to make sure your app is listening on the PORT variable as well rather than hardcoding
Target port seem to direct traffic from the domain to the port the app uses. eg example.com -> :8000. I am wondering how I can receive traffic from a different port on the public domain eg example.com:25 -> :25.
4 months ago
You do not have control over that. Railway’s proxy handles incoming ports
4 months ago
correct, you cannot run mail servers because you cannot specifically expose port 25 on tcp
4 months ago
!s
Status changed to Solved adam • 4 months ago