-
Notifications
You must be signed in to change notification settings - Fork 161
Lua Examples
Warning: Lua support in Inbucket is still in a preview state, please expect the API and features to evolve before a full release.
By default Inbucket will load inbucket.lua
, but you may use the INBUCKET_LUA_PATH
environment variable to change that.
Inbucket allows Lua scripts to write log entries via the built-in logger
package -- API provided by loguago.
Each logging call must include a level. Only log entries greater than or equal to global INBUCKET_LOGLEVEL
environment variable will be output. In order of least to most severe, the available log levels are: debug
, info
, warn
, and error
. Inbucket will output the level info
and higher by default.
Usage: logger.<level>(message, fields)
The first parameter is the log message; you may use Lua's string.format
to interpolate values into the message, if needed. The second parameter is a table of fields that will be included in the log entry JSON data. While you are not required to add fields, the current API requires at least an empty table parameter.
local logger = require("logger")
-- The following functions all have the same signature but different names to
-- allow for log leveling.
logger.debug("message at debug level", {})
logger.info("message at info level", {})
logger.warn("message at warn level", {})
logger.error("message at error level", {})
-- Example with formatting and fields.
local orig_addr = "[email protected]"
local new_addr = "[email protected]"
logger.info(string.format("Mapping address to %q", new_addr), {address = orig_addr})
Console log output for the example above:
$ env INBUCKET_LOGLEVEL=debug ./inbucket
1:49PM INF Inbucket starting buildDate=undefined phase=startup version=undefined
1:49PM INF Loading script module=lua path=inbucket.lua phase=startup
1:49PM DBG message at debug level module=lua
1:49PM INF message at info level module=lua
1:49PM WRN message at warn level module=lua
1:49PM ERR message at error level module=lua
1:49PM INF Mapping address to "[email protected]" [email protected] module=lua
Example JSON output:
$ env INBUCKET_LOGLEVEL=debug ./inbucket -logjson
{"level":"info","phase":"startup","version":"undefined","buildDate":"undefined","time":"2023-11-13T13:54:01-08:00","message":"Inbucket starting"}
{"level":"info","module":"lua","phase":"startup","path":"inbucket.lua","time":"2023-11-13T13:54:01-08:00","message":"Loading script"}
{"level":"debug","module":"lua","time":"2023-11-13T13:54:01-08:00","message":"message at debug level"}
{"level":"info","module":"lua","time":"2023-11-13T13:54:01-08:00","message":"message at info level"}
{"level":"warn","module":"lua","time":"2023-11-13T13:54:01-08:00","message":"message at warn level"}
{"level":"error","module":"lua","time":"2023-11-13T13:54:01-08:00","message":"message at error level"}
{"level":"info","module":"lua","address":"[email protected]","time":"2023-11-13T13:54:01-08:00","message":"Mapping address to \"[email protected]\""}
This event fires when Inbucket is evaluating an SMTP "MAIL FROM" command.
Denies mail that is not from james*@example.com:
function inbucket.before.mail_accepted(from_localpart, from_domain)
print(string.format("\n### inspecting from %s@%s", from_localpart, from_domain))
if from_domain ~= "example.com" then
-- Only allow example.com mail
return false
end
if string.find(from_localpart, "james") ~= 1 then
-- Only allow mailboxes starting with 'james'
return false
end
return true
end
Prints some info when a message is deleted:
function inbucket.after.message_deleted(msg)
print(string.format("\n### deleted ID %s (subj %q) from mailbox %s", msg.id, msg.subject, msg.mailbox))
end
This event fires after Inbucket has accepted a message, but before it has been stored.
Changes the destination mailbox to test
from swaks
, and does not store mail for
alternate
.
local logger = require("logger")
-- Original mailbox name on left, new on right.
-- `false` causes mail for that box to be discarded.
local mailbox_mapping = {
["swaks"] = "test",
["alternate"] = false,
}
function inbucket.before.message_stored(msg)
local made_changes = false
local new_mailboxes = {}
-- Loop over original recipient mailboxes for this message, building up list
-- of new_mailboxes.
for index, orig_box in ipairs(msg.mailboxes) do
local new_box = mailbox_mapping[orig_box]
if new_box then
logger.info(string.format("Mapping mailbox %q to %q", orig_box, new_box), {})
new_mailboxes[#new_mailboxes+1] = new_box
made_changes = true
elseif new_box == false then
logger.info(string.format("Discarding mail for %q", orig_box), {})
made_changes = true
else
-- No match, continue using the original value for this mailbox.
new_mailboxes[#new_mailboxes+1] = orig_box
end
end
if made_changes then
-- Recipient mailbox list was changed, return updated msg.
logger.info(
string.format("New mailboxes: %s", table.concat(new_mailboxes, ", ")),
{count = #new_mailboxes})
msg.mailboxes = new_mailboxes
return msg
end
-- No changes, return nil to signal inbucket to use original msg.
return nil
end
Prints metadata of stored messages to STDOUT:
function inbucket.after.message_stored(msg)
print("\n## message_stored ##")
print(string.format("mailbox: %s", msg.mailbox))
print(string.format("id: %s", msg.id))
print(string.format("from: %q <%s>",
msg.from.name, msg.from.address))
for i, to in ipairs(msg.to) do
print(string.format("to[%s]: %q <%s>", i, to.name, to.address))
end
print(string.format("date: %s", os.date("%c", msg.date)))
print(string.format("subject: %s", msg.subject))
end
Makes a JSON encoded POST to a web service:
local http = require("http")
local json = require("json")
BASE_URL = "https://myapi.example.com"
function inbucket.after.message_stored(msg)
local request = json.encode {
subject = string.format("Mail from %q", msg.from.address),
body = msg.subject
}
assert(http.post(BASE_URL .. "/notify/text", {
headers = { ["Content-Type"] = "application/json" },
body = request,
}))
end
Writes data to temporary file and runs external shell command:
function inbucket.after.message_stored(msg)
local content = string.format("%q,%q", msg.from, msg.subject)
-- Write content to temporary file.
local fnam = os.tmpname()
local f = assert(io.open(fnam, "w+"))
assert(f:write(content))
f:close()
local cmd = string.format("cat %q", fnam)
print(string.format("\n### running %s ###", cmd))
local status = os.execute(cmd)
if status ~= 0 then
error("command failed: " .. cmd)
end
print("\n")
end