### Configure STARTTLS in production.js Source: https://github.com/zone-eu/zone-mta/wiki/Setting-up-TLS-or--STARTTLS Example configuration for the feeder server to enable STARTTLS on port 587 using Let's Encrypt certificates for 'example.com'. The keys are read from disk before the user is downgraded, allowing access to protected files when starting as root. ```javascript module.exports = { user: 'nobody', group: 'nogroup', feeder: { port: 587, starttls: true, key: '/etc/letsencrypt/live/example.com/privkey.pem', cert: '/etc/letsencrypt/live/example.com/fullchain.pem' } } ``` -------------------------------- ### Create ZoneMTA application Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Commands to clone the template repository, install dependencies, and start the application. ```bash $ git clone git://github.com/zone-eu/zone-mta-template.git $ cd zone-mta-template $ npm install eslint --save-dev $ npm init $ npm install --production $ npm start ``` -------------------------------- ### Enable plugin in multiple contexts Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Example of configuring a plugin to load in 'main', 'receiver', and 'sender' contexts. ```json { "plugins": { "user/my-plugin": { "enabled": ["main", "receiver", "sender"] } } } ``` -------------------------------- ### sender:connect Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called before connection is tried to open against the MX. If the options object includes a property `socket` after hook is finished, then this socket object is used to start the SMTP session. Do not use this for tasks that have to be performed for every message as the connection may get cached and reused. This hook may also get called multiple times for a single message if the first MX IP is not accessible. ```APIDOC ## Hook: sender:connect ### Description Called before connection is tried to open against the MX. If the options object includes a property `socket` after hook is finished, then this socket object is used to start the SMTP session. Do not use this for tasks that have to be performed for every message as the connection may get cached and reused. This hook may also get called multiple times for a single message if the first MX IP is not accessible. ### Context 'sender' (requires `enabled` to include 'sender') ### Arguments - **delivery** (object) - The delivery object. - **options** (object) - The connection options. Can include `socket` property to override the socket. ### Usage Register a hook using `app.addHook('sender:connect', async (delivery, options) => { ... })`. ``` -------------------------------- ### Register a hook using async/await Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Example of using an async function for a hook handler. ```javascript app.addHook('smtp:auth', async (a, b) => { // do something }); ``` -------------------------------- ### Initialize a plugin in JavaScript Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Example of a plugin file structure using the init method to access configuration values. ```javascript module.exports.init = async app => { console.log(app.config['my-value']); // 123 }; ``` -------------------------------- ### Configure a plugin in config.json Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Example of enabling a plugin and passing custom configuration values in the plugins section. ```json { "plugins": { "my-plugin": { "enabled": true, "my-value": 123 } } } ``` -------------------------------- ### Example of address validation and rewriting Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Demonstrates retrieving addresses from headers and using the set method to update them. ```javascript // From: Sender Name let from = app.validateAddress(envelope.headers, 'from'); from.addresses; // [{name:'Sender Name', address: 'sender@example.com'}] from.set('My Name '); // From: first@blurdybloop.com from.set({ name: 'My Name', address: 'first@blurdybloop.com' }); // From: first@blurdybloop.com ``` -------------------------------- ### Express.js bounce handler example Source: https://github.com/zone-eu/zone-mta/wiki/Receiving-bounce-notifications An example of an Express.js route handler that receives and logs bounce notification data sent by ZoneMTA. ```javascript server.post('/test-bounce', function(req, res) { console.log('Failed sending from %s to %s. Server responded with "%s"', req.body.returnPath, req.body.to, req.body.response); res.send('OK'); }); ``` -------------------------------- ### GET /metrics Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Exposes Prometheus metrics for monitoring ZoneMTA. ```APIDOC ## [GET] /metrics ### Description Exposes Prometheus metrics for monitoring ZoneMTA. ### Method GET ### Endpoint /metrics ### Response #### Success Response (200) - **zonemta_delivery_status** (counter) - Counters for delivery statuses with labels `result` (delivered, rejected, deferred) and `zone`. - **zonemta_message_push** (counter) - Count of stored emails accepted for delivery. - **zonemta_message_drop** (counter) - Count of emails not accepted for delivery. - **zonemta_queue_size** (gauge) - Current queue size with labels `type` (queued, deferred). - **zonemta_blacklisted** (gauge) - Currently blacklisted domain:localAddress combos. - **zonemta_connection_reuses** (counter) - How often connections are reused since last restart. - **zonemta_connection_pool_size** (gauge) - Connection pool size of this instance. #### Response Example (No example provided in source) ``` -------------------------------- ### Address object structure Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Example of an address object containing an email address and extension arguments. ```javascript let address = { address: 'sender@example.com', args: { SIZE: '12345', RET: 'HDRS' } }; ``` -------------------------------- ### GET /queued/{type}/{zone} Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Lists the first 1000 messages queued or deferred for a specified queue. ```APIDOC ## GET /queued/{type}/{zone} ### Description Lists the first 1000 messages queued or deferred for a queue. ### Method GET ### Endpoint /queued/{type}/{zone} ### Parameters #### Path Parameters - **type** (string) - Required - The type of queue: "active" or "deferred". - **zone** (string) - Required - The name of the zone (e.g., "default"). ### Response #### Success Response (200) - **list** (array) - Array of message objects. - **id** (string) - Message ID. - **zone** (string) - Zone name. - **recipient** (string) - Recipient email address. #### Response Example { "list": [ { "id": "157ca04cd5c000ddea", "zone": "default", "recipient": "example@example.com" } ] } ``` -------------------------------- ### Register a hook using callbacks Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Example of using a callback function for a hook handler, requiring a call to next() to continue processing. ```javascript app.addHook('smtp:auth', async (a, b, next) => { // do something next(); // or `next(error)` for failures }); ``` -------------------------------- ### Prometheus static config for ZoneMTA Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Example Prometheus configuration snippet to link the ZoneMTA server. The server should be targeted at localhost:8080. ```yaml static_configs: - targets: ['localhost:8080'] ``` -------------------------------- ### GET /counter/zone/ Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Retrieves counters for all zones, showing queued and deferred message counts. ```APIDOC ## GET /counter/zone/ ### Description Returns counters for all zones. ### Method GET ### Endpoint /counter/zone/ ### Response #### Success Response (200) - **active** (object) - Contains counter for active (queued) messages. - **rows** (number) - Number of active messages. - **deferred** (object) - Contains counter for deferred messages. - **rows** (number) - Number of deferred messages. #### Response Example { "active": { "rows": 13 }, "deferred": { "rows": 17 } } ``` -------------------------------- ### GET /fetch/{id} Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Fetches the entire message contents for a given queue ID. ```APIDOC ## GET /fetch/{id} ### Description Fetches the entire message contents for a given queue ID. ### Method GET ### Endpoint /fetch/{id} ### Parameters #### Path Parameters - **id** (string) - Required - The queue ID of the message (e.g., "1578a823de00009fbb"). ### Response #### Success Response (200) - **body** (string) - The full message content in message/rfc822 format. #### Response Example Content-Type: text/plain From: sender@example.com To: exmaple@example.com Subject: testmessage Message-ID: <4f7e73c3-009c-48c2-4b45-1cf20b2fe6d3@example.com> Date: Sat, 15 Oct 2016 20:24:54 +0000 MIME-Version: 1.0 Hello world! This is a test message ... ``` -------------------------------- ### app.addAPI('GET', '/ping', (req, res, next) => { ... }) Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Registers a custom HTTP API endpoint for the plugin. The endpoint is reachable via `/plugin/`. ```APIDOC ## app.addAPI(method, path, callback) ### Description Registers a custom HTTP API endpoint for the plugin. The endpoint is reachable via `/plugin/`. The callback parameters are the same as those from restify. ### Method app.addAPI ### Parameters - **method** (string) - Required - HTTP method (e.g., 'GET', 'POST'). - **path** (string) - Required - The endpoint path relative to the plugin base path. - **callback** (function) - Required - The callback function with parameters `(req, res, next)`. ### Example ```javascript app.addAPI('GET', '/ping', (req, res, next) => { res.end('my awesome plugin'); next(); }); ``` ### Notes - Endpoints are reachable via `/plugin/`, e.g., `/plugin/awesome-plugin/test`. - The callback parameters are the same as those from `restify`. ``` -------------------------------- ### app.addAPI('GET', '/ping', (req, res, next) => { ... }) Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Registers a custom HTTP API endpoint for the plugin. The endpoint is reachable via `/plugin/`. ```APIDOC ## app.addAPI(method, path, callback) ### Description Registers a custom HTTP API endpoint for the plugin. The endpoint is reachable via `/plugin/`. ### Method app.addAPI ### Parameters - **method** (string) - Required - HTTP method (e.g., 'GET'). - **path** (string) - Required - The endpoint path relative to the plugin base path. - **callback** (function) - Required - The callback function with parameters `(req, res, next)` from restify. ### Example ```javascript app.addAPI('GET', '/ping', (req, res, next) => { res.end('my awesome plugin'); next(); }); ``` ### Notes - Endpoints are reachable via `/plugin/`, e.g., `/plugin/awesome-plugin/test`. - The callback parameters are the same as those from restify. ``` -------------------------------- ### Start ZoneMTA with increased memory limit Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Use the --max-old-space-size flag to increase the memory available to the Node.js process, which is recommended for handling large SMTP envelopes. ```bash node --max-old-space-size=8192 app.js ``` -------------------------------- ### GET /message/{id} Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Retrieves the current status of a message in the queue, including general information and recipient statuses. ```APIDOC ## GET /message/{id} ### Description Returns the current status of a message in the queue, including general information and recipient statuses. ### Method GET ### Endpoint /message/{id} ### Parameters #### Path Parameters - **id** (string) - Required - The queue ID of the message (e.g., "1578a823de00009fbb"). ### Response #### Success Response (200) - **meta** (object) - General message information. - **id** (string) - Message ID. - **interface** (string) - Interface type. - **from** (string) - Sender email. - **to** (array) - Recipient emails. - **origin** (string) - Origin IP. - **originhost** (string) - Origin host. - **transhost** (string) - Transport host. - **transtype** (string) - Transport type. - **time** (number) - Timestamp. - **dkim** (object) - DKIM signature info. - **headers** (array) - Message headers. - **messageId** (string) - Message-ID header. - **date** (string) - Date header. - **parsedEnvelope** (object) - Parsed envelope. - **bodySize** (number) - Body size in bytes. - **created** (number) - Creation timestamp. - **messages** (array) - List of recipient statuses. - **id** (string) - Message ID. - **seq** (string) - Sequence number. - **zone** (string) - Zone name. - **recipient** (string) - Recipient email. - **status** (string) - Status (e.g., "DEFERRED"). - **deferred** (object) - Deferred details. - **first** (number) - First deferral timestamp. - **count** (number) - Deferral count. - **last** (number) - Last deferral timestamp. - **next** (number) - Next attempt timestamp. - **response** (string) - Deferral response. #### Response Example { "meta": { "id": "1578a823de00009fbb", "interface": "feeder", "from": "sender@example.com", "to": ["recipient1@example.com", "recipient2@example.com"], "origin": "127.0.0.1", "originhost": "[127.0.0.1]", "transhost": "foo", "transtype": "ESMTP", "time": 1475497588281, "dkim": { "hashAlgo": "sha256", "bodyHash": "HAuESLcsVfL2FGQCUtFOwTL6Ax18XDXZO2vOeAz+DpI=" }, "headers": [ { "key": "date", "line": "Date: Mon, 03 Oct 2016 12:26:32 +0000" }, { "key": "from", "line": "From: Sender " }, { "key": "message-id", "line": "Message-ID: <95dc84ae-ff9e-4e95-aa75-8ee707bc018d@example.com>" }, { "key": "subject", "line": "subject: test" } ], "messageId": "<95dc84ae-ff9e-4e95-aa75-8ee707bc018d@example.com>", "date": "Mon, 03 Oct 2016 12:26:32 +0000", "parsedEnvelope": { "from": "sender@example.com", "to": [], "cc": [], "bcc": [], "replyTo": false, "sender": false }, "bodySize": 3458, "created": 1475497593204 }, "messages": [ { "id": "1578a823de00009fbb", "seq": "002", "zone": "default", "recipient": "recipient1@example.com", "status": "DEFERRED", "deferred": { "first": 1475499253068, "count": 2, "last": 1475499774161, "next": 1475501274161, "response": "450 4.3.2 Service currently unavailable" } } ] } ``` -------------------------------- ### GET /counter/zone/{zone} Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Retrieves the current status of a sending zone, including counters for queued and deferred messages. ```APIDOC ## GET /counter/zone/{zone} ### Description Returns the current state of a sending zone, including counters for queued and deferred messages. ### Method GET ### Endpoint /counter/zone/{zone} ### Parameters #### Path Parameters - **zone** (string) - Required - The name of the zone (e.g., "default"). ### Response #### Success Response (200) - **active** (object) - Contains counter for active (queued) messages. - **rows** (number) - Number of active messages. - **deferred** (object) - Contains counter for deferred messages. - **rows** (number) - Number of deferred messages. #### Response Example { "active": { "rows": 13 }, "deferred": { "rows": 17 } } ``` -------------------------------- ### check-bounce CLI usage Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Reads an SMTP error response from stdin and returns bounce information. The example pipes a multi-line SMTP error message to check-bounce and shows the parsed output with action, message, category, code, and status. ```bash $ echo "552-5.7.0 This message was blocked because its content presents a potential 552-5.7.0 security issue. Please visit 552-5.7.0 http://support.google.com/mail/bin/answer.py?answer=6590 to review our 552 5.7.0 message content and attachment content guidelines. cp3si16622595oec.101 - gsmtp" | check-bounce > data : 552-5.7.0 This message was blocked because its content presents a potential > 552-5.7.0 security issue. Please visit > 552-5.7.0 http://support.google.com/mail/bin/answer.py?answer=6590 to review our > 552 5.7.0 message content and attachment content guidelines. cp3si16622595oec.101 - gsmtp > action : reject > message : Suspicious attachment > category : virus > code : 552 > status : 5.7.0 ``` -------------------------------- ### Fetch Prometheus metrics via curl Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Retrieves the Prometheus metrics exposed by ZoneMTA. Sends a GET request to /metrics. The response contains various metrics including delivery statuses, queue sizes, and connection pool information. ```bash curl http://localhost:8080/metrics ``` -------------------------------- ### Express.js rewriter for HTML rewriting endpoint Source: https://github.com/zone-eu/zone-mta/wiki/How-to-use-HTML-rewrite? Example HTTP endpoint that receives the HTML content and appends a paragraph. The request is a POST with content type application/x-www-form-urlencoded, and the response is sent as the rewritten HTML. ```javascript server.post('/test-rewrite', function(req, res) { let updatedHtml = req.body.html + '

hello world!

'; res.send(updatedHtml); }); ``` -------------------------------- ### GET /suppressionlist Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Returns the list of suppressed recipient addresses and domains. ```APIDOC ## GET /suppressionlist ### Description Returns the list of suppressed recipient addresses and domains. ### Method GET ### Endpoint /suppressionlist ### Response #### Success Response (200) - **suppressed** (array) - Array of suppression entries. - **id** (string) - Suppression entry ID. - **address** (string) - Suppressed email address (if address suppression). - **domain** (string) - Suppressed domain (if domain suppression). #### Response Example { "suppressed": [ { "id": "58da63cc77ebe70b883bec2d", "address": "suppressed@address.com" }, { "id": "58da641f77ebe70b883bec2e", "domain": "suppressed-domain.com" } ] } ``` -------------------------------- ### Configure IP Warm-up in default.js Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Use the ratio option in the pools configuration to assign a lower load share to a new IP address. ```javascript { pools: { default: [ {name: 'host1.example.com', address: '1.2.3.1'}, {name: 'host2.example.com', address: '1.2.3.2'}, {name: 'host3.example.com', address: '1.2.3.3'}, // the next address gets only 5% of the messages to handle {name: 'warmup.example.com', address: '1.2.3.4', ratio: 1/20} ] } } ``` -------------------------------- ### Configure IP Warm-up in pools.toml Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Define the ipWarmPool configuration in TOML format to manage IP load distribution. ```toml [[ipWarmPool]] address="1.2.3.1" name="host1.example.com" [[ipWarmPool]] address="1.2.3.2" name="host2.example.com" [[ipWarmPool]] address="1.2.3.3" name="warmup.example.com" ratio="0.05" ``` -------------------------------- ### Implement plugin with analyzer and message:store hook Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Demonstrates using a WeakMap to store state during analysis and rejecting a message via the message:store hook. ```javascript module.exports.title = 'My Awesome Plugin'; module.exports.init = async app => { let state = new WeakMap(); app.addAnalyzerHook((envelope, source, destination) => { // store a random boolean to the WeakMap structure using envelope value as the key state.set(envelope, Math.random() >= 0.5); source.pipe(destination); }); app.addHook('message:store', async (envelope, body) => { // check from the WeakMap structure if there's a `true` for the envelope if (!state.get(envelope)) { // do not accept the message for delivery throw new Error('You have been randomly denied'); } }); done(); }; ``` -------------------------------- ### smtp:init Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called when the SMTP interface has been initialized. This hook is triggered in the 'receiver' context. ```APIDOC ## Hook: smtp:init ### Description Called when the SMTP interface has been initialized. ### Context 'receiver' (requires `enabled` to be `true`, `'receiver'`, or include `'receiver'`) ### Arguments - **server** (object) - The SMTP server object. ### Usage Register a hook using `app.addHook('smtp:init', async (server) => { ... })`. ``` -------------------------------- ### Modifying the envelope object Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Example of modifying the envelope object within an smtp:data hook and accessing the custom property in a later sender:fetch hook. ```javascript app.addHook('smtp:data', async (envelope, session) => { // Override existing source IP with something else. // This value ends up in the Received header envelope.origin = '1.2.3.4'; // Add new custom property envelope.my_custom_value = 123; }); app.addHook('sender:fetch', delivery => { console.log(envelope.my_custom_value); // 123; }); ``` -------------------------------- ### Enable HTTP Delivery via Plugin Hook Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Set the http option to true and define a targetUrl within a sender:fetch hook to redirect delivery to an HTTP endpoint. ```javascript app.addHook('sender:fetch', (delivery, next) => { delivery.http = true; delivery.targetUrl = 'http://requestb.in/1ed6q7l1'; next(); }); ``` -------------------------------- ### List queued messages Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Retrieve a list of the first 1000 messages in a queue. Replace 'active' with 'deferred' in the URL to list deferred messages. ```bash curl http://localhost:8080/queued/active/default ``` ```json { "list": [ { "id": "157ca04cd5c000ddea", "zone": "default", "recipient": "example@example.com" } ] } ``` -------------------------------- ### headers.get(key) Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Returns an array of strings with all header rows for the selected key. Each string is a full header line including the key name. ```APIDOC ## headers.get(key) ### Description Returns an array of strings with all header rows for the selected key. Each string is a full header line including the key name. ### Method headers.get ### Parameters - **key** (string) - Required - The header key to retrieve. ### Returns - **Array** - Array of full header lines, e.g., `["Subject: This is subject line"]`. ``` -------------------------------- ### Define a custom ZoneMTA plugin Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Plugins must export a title and an async init method to register hooks. For versions prior to v3.0.0, use the callback-based init method instead. ```javascript module.exports.title = 'My Awesome Plugin'; module.exports.init = async app => { // handle plugin initialization app.addHook(...); }; ``` -------------------------------- ### Throw SMTP error with responseCode in smtp:auth hook Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Use this snippet inside an SMTP hook to reject a client command by throwing an Error with a responseCode property. The error message is returned to the client as the SMTP response, and the hook processing stops at the first error. In this example, the 'smtp:auth' hook throws an error with responseCode 535 to indicate invalid credentials. ```javascript app.addHook('smtp:auth', async (auth, session) => { let err = new Error('Invalid password'); err.responseCode = 535; throw err; }); ``` -------------------------------- ### headers.getFirst(key) Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Returns the string value of the specified header key, without the key name. ```APIDOC ## headers.getFirst(key) ### Description Returns the string value of the specified header key, without the key name. ### Method headers.getFirst ### Parameters - **key** (string) - Required - The header key to retrieve. ### Returns - **string** - The header value, e.g., `"This is subject line"`. ``` -------------------------------- ### Configure routing by header value Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Define specific header values in the Sending Zone configuration using the routingHeaders option. ```javascript 'sending-zone': { ... routingHeaders: { 'x-user-id': '123' } } ``` -------------------------------- ### Configure ZoneMTA for local DNS caching Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Disable internal DNS caching and point to a local DNS server like dnsmasq for improved performance. ```json "dns": { "caching": false, "nameservers": ["127.0.0.1"] } ``` -------------------------------- ### Register a hook Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Basic syntax for adding a hook to the application. ```javascript app.addHook(name, handler); ``` -------------------------------- ### sender:connection Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called once the system has established a new or reusing an existing connection. ```APIDOC ## Hook: sender:connection ### Description Called once the system has established a new or reusing an existing connection. ### Context 'sender' (requires `enabled` to include 'sender') ### Arguments - **delivery** (object) - The delivery object. - **connection** (object) - The SMTP connection object. ### Usage Register a hook using `app.addHook('sender:connection', async (delivery, connection) => { ... })`. ``` -------------------------------- ### smtp:connect Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called when the client connects to the interface. This hook is triggered in the 'receiver' context. ```APIDOC ## Hook: smtp:connect ### Description Called when the client connects to the interface. ### Context 'receiver' (requires `enabled` to be `true`, `'receiver'`, or include `'receiver'`) ### Arguments - **session** (object) - The session object. ### Usage Register a hook using `app.addHook('smtp:connect', async (session) => { ... })`. ``` -------------------------------- ### Route by X-Sending-Zone header Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Use the X-Sending-Zone header to preselect a specific Sending Zone for a message. ```text X-Sending-Zone: zone-identifier ``` -------------------------------- ### sender:connecting Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called when a connection has been requested. This is mainly meant for logging. ```APIDOC ## Hook: sender:connecting ### Description Called when a connection has been requested. This is mainly meant for logging. ### Context 'sender' (requires `enabled` to include 'sender') ### Arguments - **delivery** (object) - The delivery object. ### Usage Register a hook using `app.addHook('sender:connecting', async (delivery) => { ... })`. ``` -------------------------------- ### Express.js configuration manager for ZoneMTA DKIM Source: https://github.com/zone-eu/zone-mta/wiki/Handling-DKIM-keys Express.js endpoint responding to ZoneMTA's configuration request. Returns a DKIM key configuration when the authenticated user is 'trusted-user', otherwise an empty JSON object. The response includes the domain name, key selector, and private key for signing the message. ```javascript server.post('/test-get-config', function(req, res) { let response = {}; if(req.body.user === 'trusted-user'){ response.dkim = { keys: { domainName: 'example.com', keySelector: 'key-selector', privateKey: '-----BEGIN RSA PRIVATE KEY-----....' } } } res.json(response); }); ``` -------------------------------- ### message:queue Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called when message is processed and ready to be pushed to queue. You should not modify the `body` stream in any way, otherwise you break the body hash. ```APIDOC ## Hook: message:queue ### Description Called when message is processed and ready to be pushed to queue. You should not modify the `body` stream in any way, otherwise you break the body hash. ### Context 'receiver' (requires `enabled` to be `true`, `'receiver'`, or include `'receiver'`) ### Arguments - **envelope** (object) - The email envelope. - **messageInfo** (object) - The message information. ### Usage Register a hook using `app.addHook('message:queue', async (envelope, messageInfo) => { ... })`. ``` -------------------------------- ### POST Configuration Management URL Source: https://github.com/zone-eu/zone-mta/wiki/Handling-DKIM-keys ZoneMTA calls the configuration management URL to retrieve DKIM keys and other sending options for each message. The request is a POST with form-encoded parameters, and the response is a JSON object with optional fields. ```APIDOC ## [POST] [Configuration Management URL] ### Description ZoneMTA makes an HTTP POST request to the configuration management URL to retrieve DKIM keys and other sending options for the upcoming message. The response should be a JSON object with optional fields. ### Method POST ### Endpoint The URL from the `getSenderConfig` configuration option. ### Parameters #### Request Body (application/x-www-form-urlencoded) - **from** (string) - Required - The MAIL FROM address. - **origin** (string) - Required - The IP address of the connecting client. - **transtype** (string) - Required - Indicates if the mail is sent from 'SMTP' or 'HTTP'. - **user** (string) - Required - The username if the user is authenticated, or an empty string if not. ### Request Example ``` from=sender@example.com&origin=192.168.1.1&transtype=SMTP&user=trusted-user ``` ### Response #### Success Response (200) - **rewriteFrom** (string) - Optional - An email address to use instead of the address provided by MAIL FROM and the message From: header. - **deferDelivery** (number) - Optional - A timestamp in milliseconds when the message should be delivered. - **dkim** (object) - Optional - An object with the following properties: - **hashAlgo** (string) - Optional - The algorithm to use (e.g., "sha256") when signing messages. - **keys** (object or array) - Optional - An object or an array of objects with keys to use for signing this message. - **domainName** (string) - Optional - The domain name to use in the signature. - **keySelector** (string) - Optional - The selector to use in the signature. - **privateKey** (string) - Optional - The private key to use for signing. #### Response Example ```json { "rewriteFrom": "sender@example.com", "deferDelivery": 1700000000000, "dkim": { "hashAlgo": "sha256", "keys": { "domainName": "example.com", "keySelector": "key-selector", "privateKey": "-----BEGIN RSA PRIVATE KEY-----..." } } } ``` ``` -------------------------------- ### app.addHook('message:store', handler) Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Registers a hook for the 'message:store' event. The handler receives the envelope and body, and can throw an error to reject the message from delivery. ```APIDOC ## app.addHook('message:store', handler) ### Description Registers a hook for the 'message:store' event. This hook is called when a message is about to be stored. The handler can throw an error to reject the message from delivery. ### Method N/A (JavaScript API) ### Endpoint N/A (JavaScript API) ### Parameters #### Function Parameters - **handler** (function) - Required - An async function that processes the message. It receives the following arguments: - **envelope** (object) - Required - An object with sender and recipient info. - **body** (object) - Required - The message body. ### Request Example ```javascript app.addHook('message:store', async (envelope, body) => { // check stored information and reject if needed throw new Error('You have been randomly denied'); }); ``` ### Response #### Success Response No return value. #### Error Response Throws an error to reject the message. ``` -------------------------------- ### Registering a plugin HTTP API endpoint Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Use app.addAPI to register custom endpoints. The callback parameters follow the restify framework standards. ```javascript app.addAPI('GET', '/ping', (req, res, next) => { res.end('my awesome plugin'); next(); }); ``` -------------------------------- ### queue:route Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called before a message entry is stored to message index. This is the last chance to edit message routing for a single recipient. Message for this specific recipient is routed to `routing.deliveryZone`. If this zone does not exist, then your message is never sent and sits in the queue forever. ```APIDOC ## Hook: queue:route ### Description Called before a message entry is stored to message index. This is the last chance to edit message routing for a single recipient. Message for this specific recipient is routed to `routing.deliveryZone`. If this zone does not exist, then your message is never sent and sits in the queue forever. ### Context 'main' (requires `enabled` to include 'main') ### Arguments - **envelope** (object) - The email envelope. - **routing** (object) - The routing object. Contains `deliveryZone` property that can be modified. ### Usage Register a hook using `app.addHook('queue:route', async (envelope, routing) => { ... })`. ``` -------------------------------- ### Configure routing by sender domain Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Use the senderDomains option in the Zone configuration to route messages based on the sender's From domain. ```javascript 'sending-zone': { ... senderDomains: ['example.com'] } ``` -------------------------------- ### sender:connected Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called when a new connection has been established. This is mainly meant for logging. ```APIDOC ## Hook: sender:connected ### Description Called when a new connection has been established. This is mainly meant for logging. ### Context 'sender' (requires `enabled` to include 'sender') ### Arguments - **delivery** (object) - The delivery object. - **connection** (object) - The SMTP connection object. - **options** (object) - The connection options. - **secure** (boolean) - Whether the connection is secure. ### Usage Register a hook using `app.addHook('sender:connected', async (delivery, connection, options, secure) => { ... })`. ``` -------------------------------- ### log:entry Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called when something happens with a message. The entry argument contains the log entry, and entry.action indicates the exact cause of the log event. ```APIDOC ## Hook: log:entry ### Description Called when something happens with a message. See `entry.action` for exact cause of the log event. ### Context Global (any context) ### Arguments - **entry** (object) - The log entry object. Contains `action` property indicating the cause. ### Usage Register a hook using `app.addHook('log:entry', async (entry) => { ... })`. ``` -------------------------------- ### smtp:sni Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called when the client tries to establish a SNI session. Set `data.secureContext` value if you want to override hostname specific secure context. ```APIDOC ## Hook: smtp:sni ### Description Called when the client tries to establish a SNI session. Set `data.secureContext` value if you want to override hostname specific secure context. ### Context 'receiver' (requires `enabled` to be `true`, `'receiver'`, or include `'receiver'`) ### Arguments - **servername** (string) - The SNI servername. - **data** (object) - The data object. Contains `secureContext` property that can be set. ### Usage Register a hook using `app.addHook('smtp:sni', async (servername, data) => { ... })`. ``` -------------------------------- ### app.validateAddress(headers, key) Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Validates and optionally rewrites an address header. Returns an object with the addresses found and a set method to override the header. ```APIDOC ## app.validateAddress(headers, key) ### Description Validates and optionally rewrites an address header. Returns an object with the addresses found and a set method to override the header. ### Method N/A (JavaScript method) ### Endpoint N/A ### Parameters - **headers** (Headers object) - Required - Headers object, e.g., `delivery.headers` or `envelope.headers`. - **key** (string) - Required - Header key to check, e.g., `from`, `to`, `cc`. ### Request Example ```javascript let from = app.validateAddress(envelope.headers, 'from'); ``` ### Response Returns an object with: - **addresses** (array) - Array of e-mail addresses found for that key (structured values). - **set** (function) - Method that overrides the original key with new addresses. Accepts a string or an object with `name` and `address`. ### Response Example ```javascript from.addresses; // [{name:'Sender Name', address: 'sender@example.com'}] from.set('My Name '); // From: first@blurdybloop.com from.set({ name: 'My Name', address: 'first@blurdybloop.com' }); // From: first@blurdybloop.com ``` ``` -------------------------------- ### sender:fetch Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called when message is retrieved from queue for delivery. This hook is triggered in the 'sender' context. ```APIDOC ## Hook: sender:fetch ### Description Called when message is retrieved from queue for delivery. ### Context 'sender' (requires `enabled` to include 'sender') ### Arguments - **delivery** (object) - The delivery object. ### Usage Register a hook using `app.addHook('sender:fetch', async (delivery) => { ... })`. ``` -------------------------------- ### sender:tlserror Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called after a TLS connection failed against the MX. ```APIDOC ## Hook: sender:tlserror ### Description Called after a TLS connection failed against the MX. ### Context 'sender' (requires `enabled` to include 'sender') ### Arguments - **delivery** (object) - The delivery object. - **options** (object) - The connection options. ### Usage Register a hook using `app.addHook('sender:tlserror', async (delivery, options) => { ... })`. ``` -------------------------------- ### headers.update(key, value) Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Replaces a header value for the specified key. ```APIDOC ## headers.update(key, value) ### Description Replaces a header value for the specified key. ### Method headers.update ### Parameters - **key** (string) - Required - The header key. - **value** (string) - Required - The new header value. ``` -------------------------------- ### smtp:auth Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called when AUTH command is issued by the client. This hook is triggered in the 'receiver' context. ```APIDOC ## Hook: smtp:auth ### Description Called when AUTH command is issued by the client. ### Context 'receiver' (requires `enabled` to be `true`, `'receiver'`, or include `'receiver'`) ### Arguments - **auth** (object) - The authentication data. - **session** (object) - The session object. ### Usage Register a hook using `app.addHook('smtp:auth', async (auth, session) => { ... })`. ### Error Handling If an error is thrown, the error message is returned to the client as the SMTP response. To set a specific return code, use `err.responseCode`. ``` -------------------------------- ### sender:delivered Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Called after a message has been accepted by MX server. ```APIDOC ## Hook: sender:delivered ### Description Called after a message has been accepted by MX server. ### Context 'sender' (requires `enabled` to include 'sender') ### Arguments - **delivery** (object) - The delivery object. - **info** (object) - The delivery information. ### Usage Register a hook using `app.addHook('sender:delivered', async (delivery, info) => { ... })`. ``` -------------------------------- ### List suppressed addresses and domains Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Retrieve the current suppression list containing addresses and domains that are silently dropped. ```bash curl http://localhost:8080/suppressionlist ``` ```json { "suppressed": [ { "id": "58da63cc77ebe70b883bec2d", "address": "suppressed@address.com" }, { "id": "58da641f77ebe70b883bec2e", "domain": "suppressed-domain.com" } ] } ``` -------------------------------- ### Validate and rewrite address headers Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Use app.validateAddress to check and optionally overwrite address headers like 'from', 'to', or 'cc'. ```javascript app.validateAddress(headers, key); ``` -------------------------------- ### Configure routing by recipient domain Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Use the recipientDomains option in the Zone configuration to route messages based on the recipient's domain. ```javascript 'sending-zone': { ... recipientDomains: ['gmail.com', 'kreata.ee'] } ``` -------------------------------- ### Implement custom SNI with smtp:sni hook Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Use the smtp:sni hook to provide custom secure contexts for specific server names during the SMTP handshake. ```js app.addHook('smtp:sni', async (servername, data) => { if (servername === 'smtp.example.com') { data.secureContext = tls.createSecureContext({ key: privateKey, cert: certData }); } }); ``` -------------------------------- ### Fetch message body Source: https://github.com/zone-eu/zone-mta/blob/master/README.md Retrieve the full contents of a message by its queue ID. The response is in message/rfc822 format. ```bash curl http://localhost:8080/fetch/1578a823de00009fbb ``` ```text Content-Type: text/plain From: sender@example.com To: exmaple@example.com Subject: testmessage Message-ID: <4f7e73c3-009c-48c2-4b45-1cf20b2fe6d3@example.com> Date: Sat, 15 Oct 2016 20:24:54 +0000 MIME-Version: 1.0 Hello world! This is a test message ... ``` -------------------------------- ### headers.add(key, value [,index]) Source: https://github.com/zone-eu/zone-mta/blob/master/plugins/README.md Adds a new header value to the specified index or to the top of the header block if index is not specified. ```APIDOC ## headers.add(key, value [,index]) ### Description Adds a new header value to the specified index or to the top of the header block if index is not specified. ### Method headers.add ### Parameters - **key** (string) - Required - The header key. - **value** (string) - Required - The header value. - **index** (number) - Optional - The index at which to insert the header. If not specified, the header is added to the top. ```