# Additional Configuration

This page contains additional system configuration options that are provided for self-managed Appmixer installations. See the [System Configuration](/appmixer-backoffice/system-configuration.md) for the rest of the configuration options. The options below can be set via the environment variables on the instances of the Appmixer engine.

## API and Worker nodes

Appmixer 6.2 introduces the ability to easily start a pod as either an API node or a worker node. If you deployed Appmixer using the Helm Chart, no action is required—the chart already supports separate API and worker nodes.

#### Running an API node only

To run a node as an API node only, pass the `--no-worker` argument in your deployment configuration (engine.yaml):

```yaml
...
    spec:
      containers:
        - args:
            - -c
            - node gridd.js --http --no-worker       ## API node only
          command:
            - /bin/sh
...            
```

#### Running a worker node only

To run a node as a worker only, do not use the `--http` argument. The pod must be started without any arguments.

You will need to create a separate deployment configuration for the worker node. This can be a copy of engine.yaml with the arguments removed:

```yaml
...
    spec:
      containers:
        - args:
            - -c
            - node gridd.js         ## Worker node only
          command:
            - /bin/sh
...            
```

## Logging

#### LOG\_LEVEL

By default set to `info`. It can be changed to `error`, `warn` or `debug`.

#### LOG\_COMPONENT\_DATA\_MESSAGE

When set to `false`, the component's input/output messages won't be logged into Elasticsearch.

{% hint style="info" %}
Important! Appmixer Insights and Designer log messages won't contain any items if logging data messages are turned off.
{% endhint %}

#### LOG\_STDOUT\_ONLY

When set to `true`, all logs are sent to stdout only and RabbitMQ connection for logging is skipped. This is useful when using log aggregation tools like [Vector](https://vector.dev/), Fluentd, or Filebeat that read logs from stdout/stderr.

By default, Appmixer sends logs to RabbitMQ, which are then consumed by Logstash and stored in Elasticsearch. When `LOG_STDOUT_ONLY` is enabled:

* All logs (system logs, component data messages, flow logs) are written to stdout as JSON
* RabbitMQ connection for logging is not established
* Logs are written asynchronously using pino transport for non-blocking output
* Log aggregation tools can collect logs directly from container stdout

**Default value:** `false`

**Example configuration:**

```yaml
  engine:
    ...
    environment:
      - LOG_STDOUT_ONLY=true
    ...
```

{% hint style="info" %}
When using `LOG_STDOUT_ONLY=true`, you need to set up an external log aggregation solution (e.g., Vector, Fluentd, Filebeat) to collect and forward logs to your logging backend (e.g., Elasticsearch, Loki, CloudWatch).
{% endhint %}

{% hint style="warning" %}
If you enable `LOG_STDOUT_ONLY` without setting up log collection, logs will only be available in container stdout/stderr and will be lost when the container restarts unless your container runtime preserves logs.
{% endhint %}

**Use Cases:**

* **Kubernetes deployments**: Use Vector as a sidecar or DaemonSet to collect logs from all pods
* **Docker Compose**: Use Fluentd or Filebeat to aggregate logs from multiple containers
* **Cloud platforms**: Send logs directly to cloud-native logging services (CloudWatch, Stackdriver, Azure Monitor)
* **Simplified architecture**: Eliminate RabbitMQ and Logstash from the logging pipeline

#### APPMIXER\_HTTPS\_PROXY and APPMIXER\_HTTP\_PROXY

Configure an HTTP proxy. All HTTP(S) requests from Appmixer will be redirected to the proxy URL.

## Token Encryption

Most of the connectors in Appmixer require user authentication. That can be represented as OAuth access tokens, API keys, or username/password combinations.

To enable token encryption, set the `ENCRYPTION_ENABLED` environment variable to `true`. With that, also set the `ENCRYPTION_SECRET` environment variable to a secret string (see below for an example on how to generate it).

```
# Turning the encryption on
ENCRYPTION_ENABLED=true
# Setting the encryption secret (generate your own!)
ENCRYPTION_SECRET=0EC0136A5187A09E21D03819A0EFFD259070CE23213B260A1444412EFD910503
```

```sh
# Generate the encryption secret
openssl enc -aes-256-gcm -k secret -P -md sha1
# copy the 'key' and put it to ENCRYPTION_SECRET
```

{% hint style="danger" %}
If you lose the encryption secret, you will not be able to recover the encrypted tokens.
{% endhint %}

## MinIO/S3

Appmixer contains a MinIO plugin. If this plugin is turned on, Appmixer will store all user files (files created either through Appmixer flows or through the [/files](/api/files.md) API) in the MinIO/S3 server.

{% hint style="warning" %}
The plugin cannot migrate existing files from MongoDB to MinIO. It has to be turned on when Appmixer is installed before the files are created. If you already have files in MongoDB and want to start using MinIO, you have to migrate the data.
{% endhint %}

To enable the plugin, add `minio` to the SYSTEM\_PLUGINS (comma-separated list of plugins) ENV variable (this variable cannot be set dynamically through the Backoffice - System Configuration):

```sh
# Turning on the plugin
SYSTEM_PLUGINS=minio

# Required variables
MINIO_ACCESS_KEY=admin
MINIO_SECRET_KEY=secretKey
MINIO_ENDPOINT=192.168.1.8   # s3.amazonaws.com to connect to AWS S3

# Optional variables
# Default value set to 80 for HTTP and 443 for HTTPS.
MINIO_PORT=9000
# Set this value to 'true' to enable secure (HTTPS) access
MINIO_USE_SSL=true
MINIO_REGION=eu-central-1
# All files will be stored by default in the 'appmixer-files' bucket
MINIO_BUCKET_NAME=appmixer-files  
```

If you want to use AWS S3, use the following permissions:

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucketMultipartUploads",
                "s3:ListBucketVersions",
                "s3:ListBucket",
                "s3:ListMultipartUploadParts",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::appmixer-files/*",
                "arn:aws:s3:::appmixer-files"
            ]
        }
    ]
}
```

## Forgot Password Service

Appmixer provides an [API](/api/user.md#forgot-password) to reset forgotten passwords. This works together with the Appmixer Studio interface (not the Appmixer SDK).

![](/files/WUD02x8W0c4lktZpoDtx)

In order to create a link that can be sent to the user, the Appmixer engine needs to know the frontend URL, there are two variables that can be set for that:

<table><thead><tr><th width="232">Key</th><th>Detail</th><th>Default value</th><th data-type="checkbox">Required</th></tr></thead><tbody><tr><td>APPMIXER_FE_URL</td><td>The Frontned URL</td><td>http://localhost:8080</td><td>false</td></tr><tr><td>RESET_PASSWORD_FE_URL_SUFFIX</td><td>URL path with the reset password form</td><td>reset-password</td><td>false</td></tr></tbody></table>

Without any changes, the *link* will be <http://localhost:8080/reset-password?code=\\{{code\\}}>.

![](/files/b6ry8BoBjjznFdJyVmYg)

That link has to be then delivered to the user. There are two ways this can be done:

#### Webhook

You can register a system webhook that will be triggered every time a user requests to change their password. The webhook URL can be registered under the key WEBHOOK\_USER\_FORGOT\_PASSWORD and the JSON object sent to that URL will be:

```json
{
  "code": 'unique code generated for identifying forgot password request',
  "email": 'email address of the user',
  "userId": 'User Id',
  "created": 'date when a user requested for forgot password',
  "link": 'Link to access forgot password page on the frontend'
}
```

You can use Appmixer to create a simple flow, that would send emails with the *reset password* link.

![](/files/U4lHLyJ3bLpKs2zF9Uuj)

#### SMTP

The other way is to configure the SMTP server, Appmixer will then send an email with the reset password link to the user's email address.

<table><thead><tr><th width="249">Key</th><th>Detail</th><th>Default value</th><th data-type="checkbox">Required</th></tr></thead><tbody><tr><td>MAIL_SMTP_HOST</td><td>SMTP server address</td><td></td><td>true</td></tr><tr><td>MAIL_SMTP_PORT</td><td>SMTP server port</td><td>465</td><td>false</td></tr><tr><td>MAIL_SMTP_USER</td><td>username</td><td></td><td>true</td></tr><tr><td>MAIL_SMTP_PASS</td><td>password</td><td></td><td>true</td></tr><tr><td>MAIL_FROM_NAME</td><td>Sender name</td><td>Appmixer</td><td>false</td></tr><tr><td>MAIL_FROM_EMAIL</td><td>Sender email</td><td>info@appmixer.com</td><td>false</td></tr><tr><td>FORGOT_PASSWORD_MAIL_SUBJECT</td><td>Reset password email subject.</td><td>Reset your password</td><td>false</td></tr><tr><td>FORGOT_PASSWORD_MAIL_BODY</td><td>The reset password email body.</td><td>See below</td><td>false</td></tr></tbody></table>

The default email body:

```
<p>Hi,</p>
<p>You have requested to reset your password. Please click on the link below to reset your password.</p>
<p><a href="{{link}}">Reset Password</a></p>
<p>If you are unable to click on the link, please copy and paste the following link into your browser:</p>
<p>{{link}}</p>
```

{% hint style="info" %}
If the forgot password webhook is configured as explained above, the Appmixer engine will not send the email to the user and it will trigger the webhook instead.
{% endhint %}

## User Signup Rate Limiting

To protect against abuse and ensure system stability, Appmixer provides rate limiting for the user signup API (`POST /user`). Rate limiting is automatically applied to unauthenticated signup requests (public user registration) and can be configured using environment variables.

{% hint style="info" %}
Rate limiting only applies to unauthenticated signups. When an authenticated admin creates a user via the API, rate limiting is bypassed.
{% endhint %}

### Configuration Variables

Appmixer implements two layers of rate limiting for user signups:

#### Email-Based Rate Limiting

Limits the number of signup attempts per email address to prevent spam and abuse.

<table><thead><tr><th width="340">Key</th><th>Detail</th><th>Default value</th><th data-type="checkbox">Required</th></tr></thead><tbody><tr><td>USER_SIGNUP_RATE_LIMIT_EMAIL</td><td>Maximum signups per email address</td><td>10</td><td>false</td></tr><tr><td>USER_SIGNUP_RATE_LIMIT_EMAIL_WINDOW_MS</td><td>Time window in milliseconds</td><td>3600000 (1 hour)</td><td>false</td></tr></tbody></table>

**Example:**

```bash
USER_SIGNUP_RATE_LIMIT_EMAIL=5
USER_SIGNUP_RATE_LIMIT_EMAIL_WINDOW_MS=1800000  # 30 minutes
```

#### IP-Based Rate Limiting

Limits the number of signup attempts from the same IP address to prevent bulk registration attacks.

<table><thead><tr><th width="340">Key</th><th>Detail</th><th>Default value</th><th data-type="checkbox">Required</th></tr></thead><tbody><tr><td>USER_SIGNUP_RATE_LIMIT_IP</td><td>Maximum signups per IP address</td><td>50</td><td>false</td></tr><tr><td>USER_SIGNUP_RATE_LIMIT_IP_WINDOW_MS</td><td>Time window in milliseconds</td><td>3600000 (1 hour)</td><td>false</td></tr></tbody></table>

**Example:**

```bash
USER_SIGNUP_RATE_LIMIT_IP=20
USER_SIGNUP_RATE_LIMIT_IP_WINDOW_MS=3600000  # 1 hour
```

### Error Responses

When a rate limit is exceeded, the API returns a `429 Too Many Requests` HTTP status with a descriptive error message:

**Email limit exceeded:**

```json
{
  "error": "Too many signup attempts for this email address. Please try again later."
}
```

**IP limit exceeded:**

```json
{
  "error": "Too many signup attempts from your IP address. Please try again later."
}
```

### Best Practices

* **Email limits** should be set lower (5-10) to prevent abuse of specific email addresses
* **IP limits** should be set higher (20-100) to account for shared IPs (corporate networks, ISPs with NAT)
* Consider your use case:
  * **Public SaaS applications**: Use stricter limits (email: 3-5, IP: 20-30)
  * **Internal/Enterprise applications**: Use more relaxed limits or disable by setting very high values
  * **Testing/Development**: Set higher limits or use authenticated user creation to bypass rate limiting

{% hint style="warning" %}
If you have legitimate use cases for high-volume user creation (e.g., bulk imports, migrations), use an authenticated admin account to create users via the API, which bypasses rate limiting entirely.
{% endhint %}

### Context Quotas <a href="#context-quotas" id="context-quotas"></a>

Components produce messages using the `context.sendJson()` function. An internal quota mechanism controls how many messages a user can produce.

This is the default configuration:

```json
// QUOTA_CONTEXT_SEND
[
    {
        # 'user' can generate 100 messages per second (across all their flows)
        "limit": 100,
        "scope": "user",
        "windowInSeconds": 1,
        "name": "send:1s"
    },
    {
        # 'user' can generate 1000 messages per minute (across all their flows)
        "limit": 1000,
        "scope": "user",
        "windowInSeconds": 60,
        "name": "send:60s"
    },
    {
        # 'user' can generate 20000 messages per day (across all their flows)
        "limit": 20000,
        "scope": "user",
        "windowInSeconds": 86400,
        "name": "send:1d"
    }
]
```

```json
// QUOTA_CONTEXT_SLOW_QUEUE
[
    {
        # 'user' can generate 100000 messages per minute (across all their flows)
        # everything that exceeds this limit will be thrown away
        "limit": 100000,
        "scope": "user",
        "windowInSeconds": 60,
        "name": "slowQueue:60s"
    },
    {
        # 'user' can generate 5000000 messages per day (across all their flows)
        # everything that exceeds this limit will be thrown away
        "limit": 5000000,
        "scope": "user",
        "windowInSeconds": 86400,
        "name": "slowQueue:1d"
    }
]
```

Each call to `context.sendJson()` increases the quota. If a limit is reached, the message is placed in a *Slow Queue*. Messages in the Slow Queue are processed at a much slower rate and only when sufficient resources are available. This ensures that one user's flows do not consume excessive resources and block other users.

#### User Scope-Based Quotas

You can define different quota limits for different user segments using the optional `userScope` field. This allows you to create tiered quota plans (e.g., free, premium, enterprise) or apply different limits based on user characteristics.

**Configuration Example:**

```json
// QUOTA_CONTEXT_SEND with user scopes
[
    {
        "limit": 10,
        "scope": "user",
        "userScope": "free",        // Free tier users
        "windowInSeconds": 1,
        "name": "send:1s"
    },
    {
        "limit": 500,
        "scope": "user",
        "userScope": "premium",     // Premium users
        "windowInSeconds": 1,
        "name": "send:1s"
    },
    {
        "limit": 2000,
        "scope": "user",
        "userScope": "enterprise",  // Enterprise users
        "windowInSeconds": 1,
        "name": "send:1s"
    },
    {
        "limit": 100,
        "scope": "user",             // Default for all users
        "windowInSeconds": 1,
        "name": "send:1s"
    }
]
```

```json
// QUOTA_CONTEXT_SLOW_QUEUE with user scopes
[
    {
        "limit": 100,
        "scope": "user",
        "userScope": "free",
        "windowInSeconds": 60,
        "name": "slowQueue:60s"
    },
    {
        "limit": 50000,
        "scope": "user",
        "userScope": "premium",
        "windowInSeconds": 60,
        "name": "slowQueue:60s"
    },
    {
        "limit": 200000,
        "scope": "user",
        "userScope": "enterprise",
        "windowInSeconds": 60,
        "name": "slowQueue:60s"
    },
    {
        "limit": 1000,
        "scope": "user",
        "windowInSeconds": 60,
        "name": "slowQueue:60s"
    }
]
```

**Rule Selection Logic:**

For each time window (`userScope` + `windowInSeconds`), the system selects the rule with the **highest limit** from:

1. Default rules (without `userScope`)
2. Rules matching the user's scopes

Note: The `scope` field is always set to `"user"` in context quota configurations.

**Example:** A user with scopes `['user', 'free', 'premium']`:

* Available rules: default (limit: 100), free (limit: 10), premium (limit: 500)
* **Result:** The premium rule is selected (limit: 500) as it provides the most generous limit

This ensures users with multiple scopes always get the most favorable quota limits.

**Setting User Scopes:**

User scopes are set when creating or updating users through the API. Users can have multiple scopes assigned:

```javascript
// Example: Creating a user with premium scope
{
    "username": "user@example.com",
    "scope": ["user", "premium"]
}
```

{% hint style="info" %}
**Use Cases for User Scopes**

* **Tiered Plans:** Implement free, premium, and enterprise tiers with different resource limits
* **Partner Programs:** Provide higher limits to strategic partners or resellers
* **Beta Testing:** Grant increased quotas to beta testers or early adopters
* **Custom Agreements:** Set specific limits for individual customers with custom contracts
  {% endhint %}

If you want to change the default values, you can use the Env variables QUOTA\_CONTEXT\_SEND and QUOTA\_CONTEXT\_SLOW\_QUEUE, you can set them in the Backoffice.

<figure><img src="https://docs.appmixer.com/~gitbook/image?url=https%3A%2F%2F1556994647-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FUaFilImHHLqJ2jPd88UX%252Fuploads%252Feihv7w1BAZ7DQJ49f4Uu%252FAppmixer_Backoffice.png%3Falt%3Dmedia%26token%3D751d7895-f6bf-4ad7-bfe9-99627d9d261b&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=4b27b65e&#x26;sv=1" alt=""><figcaption></figcaption></figure>

QUOTA\_CONTEXT\_SEND default value, you can copy\&paste this to the Backoffice and modify.

```json
[{"limit":100,"scope":"user","windowInSeconds":1,"name":"send:1s"},{"limit":1000,"scope":"user","windowInSeconds":60,"name":"send:60s"},{"limit":20000,"scope":"user","windowInSeconds":86400,"name":"send:1d"}]
```

QUOTA\_CONTEXT\_SLOW\_QUEUE default value, you can copy\&paste this to the Backoffice and modify.

```json
[{"limit":100000,"scope":"user","windowInSeconds":60,"name":"slowQueue:60s"},{"limit":500000
```

## Garbage Collectors

### Files

The garbage collector for files can be turned off with GC\_FILES\_ENABLED (set to false). The behavior can be controlled by a set of rules GC\_FILES\_RULES.

This is the default set:

```json
[
    {
        scope: 'user',

        /**
         * The GC will never delete files created in the past fileTTL (in minutes). By default,
         * set to 720 hours. The reason is to prevent the GC from removing files that could be needed by the
         * flows.
         */
        ttl: 720,   // 30 days

        /**
         * The user will be blocked to save any files if the limit is reached.
         */
        hardLimit: 1000 * 1000 * 1000 * 2   // 2 GB in bytes
    }
]
```

The value of GC\_FILES\_RULES is a stringified array of rules. There can be a different rule for each scope of users. Each rule has to have exactly 3 properties - `scope`, `ttl` and `hardLimit`.

Example:

```json
[{"scope":"user","ttl":720,"hardLimit":2000000000},{"scope":"admin","ttl":1440,"hardLimit":90000000000}]
```

In this example, the admin users can store files up to 90GB and their files will be deleted after 1440 hours (60 days).

When the users exceed the `hardLimit`, they he will not be able to create more files. To get unblocked, they must delete some of their files and get under the limit.

{% hint style="info" %}
The garbage collector does not remove files uploaded via the Files API, or files marked as `archived.`
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.appmixer.com/appmixer-self-managed/configuration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
