Custom Message Handler¶
The default FileMailboxHandler stores messages as .gemmail files. You can implement custom delivery logic by subclassing MessageHandler.
Define a Handler¶
from titlani import MessageHandler, MisfinRequest, MisfinResponse, StatusCode
class DatabaseHandler(MessageHandler):
"""Store messages in a database instead of files."""
def __init__(self, db_connection):
self.db = db_connection
async def handle_message(self, request: MisfinRequest) -> MisfinResponse:
try:
# Parse the gemmail message
message = request.parse_message()
except ValueError:
return MisfinResponse(
status=StatusCode.BAD_REQUEST,
meta="Invalid message format",
)
# Store in database
await self.db.insert_message(
mailbox=request.mailbox,
hostname=request.hostname,
senders=message.senders,
body=message.body,
received_at=message.timestamps[0] if message.timestamps else None,
)
# Return success with recipient fingerprint
return MisfinResponse(
status=StatusCode.SUCCESS,
meta="delivered",
)
Handler Contract¶
Your handle_message method receives a MisfinRequest with:
request.mailbox— Target mailbox namerequest.hostname— Target hostnamerequest.raw_message— Raw message bytesrequest.content_length— Message body lengthrequest.client_cert— Sender's TLS certificate (if provided)request.client_cert_fingerprint— Normalized fingerprint of sender's cert
Return a MisfinResponse with an appropriate status code:
| Status | When to use |
|---|---|
| 20 (SUCCESS) | Message delivered successfully |
| 45 (MAILBOX_FULL) | Mailbox cannot accept more messages |
| 51 (MAILBOX_NOT_FOUND) | Mailbox doesn't exist |
| 53 (DOMAIN_NOT_SERVICED) | Hostname not handled by this server |
| 59 (BAD_REQUEST) | Message format is invalid |
Wire to the Server¶
To use a custom handler, you currently need to build the server manually using the lower-level components:
import asyncio
import ssl
from titlani.server.protocol import MisfinServerProtocol
async def run_custom_server(handler, ssl_context, host, port):
loop = asyncio.get_event_loop()
server = await loop.create_server(
lambda: MisfinServerProtocol(handler.handle_message),
host,
port,
ssl=ssl_context,
)
async with server:
await server.serve_forever()
The MisfinServerProtocol handles the two-phase buffering (header + body), TLS, timeouts, and size limits. Your handler only needs to process the fully-parsed request.