a Reasonable Uncomplicated Mailserver Bundled with Lots of Extensions
server status Front page domains Download Rumble users Documentation settings C/C++ API modules Lua API

Rumble - Lua API

The Lua interpreter in Rumble comes with the standard libraries as well as the extensions listed below.
To add a Lua script to the mail server, simply create a Lua file and add the following command to rumble.conf:

LoadScript    /path/to/your/script    
For an example of how to use the Lua API, check out RumbleLua, which ships with the Rumble packages, or check out these simple example modules.



String manipulation


Returns the SHA-256 message digest of the string s.
This is primarilly used for creating passwords for saving new accounts or updating old ones.

local text = "some text";
local digest = text:SHA256(); -- The same as string.SHA256(text);
-- yields: b94f6f125c79e3a5ffaa826f584c10d52ada669e6762051b826b55776d05aed2



bulletstring.decode64(s) / string.encode64(s)

Decodes or encodes a string using the base-64 algorithm.

local text = "This is a test!";
local enc = text:encode64(); -- base64-encode it
local dec = enc:decode64(); -- decode the string again
print(dec); -- prints "This is a test!"





File manipulation



Returns a table containing information about the file f, similar to fstat().

local info = file.stat("myfile.cfg");
print("myfile.cfg has a size of:" .. info.size .. " bytes");
print("myfile.cfg was created at " .. info.created);



Returns true if the file exists, otherwise false.

if ( file.exists("somefile.txt") ) then
   print("somefile.txt exists!");
   print("somefile.txt doesn't exist :(");






Networking functions



Resolves the host name host into its IP address.

local IP = network.getHostByName("");
print(" is " .. IP); -- lua org is



Looks up the MX records of domain and returns a list of entries found.

local mxlist = network.getMX("");
for k, entry in pairs(mxlist) do
   print("I found mail server " .. .. " with pref " .. entry.preference);
   -- I found mail server with pref 10






Mailman functions


bulletMailman.accountExists(domain, user):

Returns true if an account exists with the literal domain and username.
Unlike Mailman.addressExists, this function will only return true if an account with the exact credentials exists.

if ( Mailman.accountExists("mydomain.tld", "someuser") ) then
    print ("someuser@mydomain.tld is a real account!");
    print ("someuser@mydomain.tld doesn't exist!");




bulletMailman.addressExists(domain, user):

Returns true if the address user@domain is either an existing email account on the server, or if any wildcard or GLOB'ed alias will pick up mail sent for it. For example, Mailman.addressExists("domain.tld", "somename") will return true if you have created the account *@domain.tld, whereas Mailman.accountExists would return false:

if ( Mailman.accountExists("mydomain.tld", "someuser") ) then
    print ("someuser@mydomain.tld is a real account!");
    print ("someuser@mydomain.tld is not a valid account!");
    if ( Mailman.addressExists("mydomain.tld", "someuser") ) then
        print ("...but it is a valid address!");





Adds domain as a new domain in the mail service if it doesn't already exist.



bulletMailman.deleteAccount([uid], domain, user):

If no UID is provided, deletes the account user@domain from the database, otherwise deletes the account with the specified UID.

Mailman.deleteAccount("mydomain.tld", "johndoe"); -- Delete johndoe@mydomain.tld
Mailman.deleteAccount(1673); -- Delete the account with UID 1673




Deletes domain from the database if it exists.

Mailman.createDomain("mynewdomain.tld"); -- Make a new domain, just for fun.
Mailman.deleteDomain("mynewdomain.tld"); -- Delete it again.




Returns a table containing all the domains in the mail server database:

for id, domain in pairs(Mailman.listDomains()) do
    print("I found " .. domain);




Returns a table containing all the accounts associated with domain:

for id, account in pairs(Mailman.listAccounts("mydomain.tld")) do
    print(("Account: %s@%s, UID: %u, type: %s"):format(, account.domain, account.uid, account.type




bulletMailman.listHeaders(uid[, folder]):

Retrieves the headers of all messages belonging to the specified user id or, optionally, the messages in the folder specified by folder:

-- get johndoe@mydomain.tld's account info
local account = Mailman.readAccount("mydomain.tld", "Johndoe");
-- Get the messages
local headers = Mailman.listHeaders(;
for id, header in pairs(headers) do
    local text = string.format("Message no. %d - From: %s, Subject: %s, Content-type: %s",
    header.from, header.subject, header['content-type']) ;
    if (not then print("This message hasn't been read yet!"); end




bulletMailman.readAccount([uid,] domain, user):

If the account, specified either by the UID or by domain and user, exists, returns a table containing the infomation about the account, otherwise returns false:

local myAccount = Mailman.readAccount("mydomain.tld", "johndoe");
if (myAccount) then
    print( string.format("I found %s@%s!",, myAccount.domain);



bulletMailman.readMail(uid, lid):

Reads the message with id lid. The account uid is required to prevent exploitation.
If found, the entire message is read and parsed into both headers, message body and
individual message parts in case of multipart messages (text/html versions, attached files
and so on). The returned message structure can be quite complex, and as such, the example
below only shows a fragment of it. For an in-depth example, please see the webmail module
included in the mail server package.

local message = Mailman.readMail(123, 23);
print("From: " .. message.headers.from);
print("Message reads:");




If account, specified as a table containing the same information as given by Mailman.readAccount, exists, saves and updates the information stored, otherwise creates a new account with the specified credentials.
Note: If you are saving or updating passwords, remember to first run them through the string.SHA256().

-- Make a new account
local newAccount = {
    name = "newuser", domain = "mydomain.tld",
    type = "mbox", password = ("mypass"):SHA256()

-- Update an existing account

local oldAccount = Mailman.readAccount("mydomain.tld", "olduser");
if (oldAccount) then = "updatedName";
   Mailman.saveAccount(oldAccount); -- updated!







bulletMailman.sendMail(from, to, message)

Creates and sends an email using the specified parameters.
The message should be properly formatted
with headers and a body.

local message = [[
Subject: Hello!

Testing 123
-- Now send the email:
Mailman.sendMail("", "", message);





Note: This API call is considered an internal mechanism and will still work even if the
BlockOutgoingMail directive is enabled in rumble.conf.



Service functions


bulletRumble.createService(luaFunction, port, [threadCount]):

Creates a TCP service on port, hooking into the function luaFunction. The optional argument
threadCount specifies the number of threads to create (default is 10). Returns true if the service
was created, otherwise returns false. If another thread has already created this service,
createService returns nil.

function acceptCall(session) -- The Lua function that accepts incoming connections.
   session:send("Content-Type: text/html\r\n\r\n");
   session:send("Hello there!");

Rumble.createService(acceptCall, 80, 10); -- Create an example web service on port 80





Returns a list of the C/C++ modules enabled on the server:

for id, module in pairs(Rumble.listModules()) do
    print("Found module: " .. module.title);
    print("What it does is: " .. module.description);
    print("It's located at: " .. module.file);




Returns the value of key in the configuration file:

local servername = Rumble.readConfig("servername");
print("Hi, this server is called " .. servername);



If suspended, resumes the service called svcName:

print("SMTP is paused!");
print("And now it's running again!");



If the service specified in name by svc exists, returns a table containing information about the
service, otherwise returns nil:

local svcinfo = Rumble.serviceInfo("smtp");
if ( svcinfo.enabled ) then
    print("SMTP is running!");
    print("So far, it's handled " .. svc.sessions .. " mail sessions");
    print("It's transmitted " .. (svc.received + svc.sent) .. " bytes of data");
    print("SMTP is disabled!");




Returns a table containing information about the server:

local info = Rumble.serverInfo();
print("This server is running on " .. server.os);
print("It's been running for " .. server.uptime .. " seconds!");




Returns a table containing the last 200 logged messages from the server:

for i, entry in pairs(Rumble.getLog()) do
    print("Entry no." .. i .. ": " .. entry);



bulletRumble.setHook(luaFunction(session, cmd), svcName, when [, cmd])

Adds a Lua hook to the service defined as svcName, hooking the function luaFunction to the
moment in a session specified by when:

  • accept: Executes the Lua function whenever a new connection is made to the service
  • close: Executes the Lua function when a connection is being closed.
  • command: if cmd is specified, executes the Lua function whenever that specific command
    is issued by the client.

Hooks can issue a return value to control the sessions. A return value of false or "failure" will terminate a connection.

local function onAccept(session, cmd)
    session:send("Welcome " ..session.address .. "!"); -- fx. Welcome!
local function onHELO(session, cmd)
    if (cmd == "") then
        session:send("You sent a bad HELO!");
        return false;
        session:send(string.format("Hello there, %s!\r\n", cmd));
        return true;
end Rumble.setHook(onAccept, "smtp", "accept"); -- hook to new SMTP connections
Rumble.setHook(onHELO, "smtp", "command", "helo"); -- hook to the HELO command




If stopped (disabled), tries to start up the service called svcName:

print("SMTP was killed!");
print("Restarting SMTP service now!");



If running or suspended, stops (kills) the service called svcName:

print("SMTP was killed!");
print("Restarting SMTP service now!");



If running, suspends the service called svcName gracefully. Unlike stopService(), it will only stop active server sessions once they have finished:

print("SMTP is paused, no new connections can be made!");
print("And now it's running again!");



Session handles


Session handles returned by hooking via Rumble.setHook or Rumble.createService have the following set of functions to communicate with the client:



Sends the message msg to the client:

function acceptCall(session)
   session:send("Hello there!"); -- Sends "Hello there!" to the client.




Returns one line of data received by the client.

function acceptCall(session)
   session:send("Hello there!"); -- Sends "Hello there!" to the client.
   local response = session:receive(); -- Get a line from the client
   session:send("You said: " .. response);




Reads numBytes of data from the client and returns the data received and the actual number of
bytes, or an empty string and a length of -1 if the operation failed:

function acceptCall(session)
   local response, length = session:receivebytes(100); -- Try to get 100 bytes.
   if ( length ~= -1 ) then
       print("We got a response!");
       print("Something went wrong, client aborted?");





bulletsession:lock() and session:unlock():

Locks or unlocks the core mutex of the entire service. This method can be used for performing
operations that would otherwise be unsafe in a threaded environment.
Rumble will automatically unlock the service once your script exits, but nonetheless, it's
important to remember to unlock it after you're done...just in case.

session:lock() -- Get a lock on the service
session:unlock(); -- Remove the lock.