Skip to content

Latest commit

 

History

216 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

AutoMessage - right message, right player, right moment

Latest release Build status Java 21 or newer; Java 25 for Minestom Minecraft 1.21 through 26.2 Six runtime targets

Modrinth downloads SpigotMC downloads Available on Hangar

Not another chat rotator. A complete announcement engine.
Targeted, localized and event-aware broadcasts for modern Minecraft servers and networks.

6 runtimes  ·  8 notice surfaces  ·  8 audience rules  ·  3 event triggers  ·  3 languages shipped  ·  one render per language  ·  no database

🧭 Why AutoMessage

⚙️ Install and configure

📖 Reference

📣 Broadcasts should earn attention

Players learn to ignore announcements when every message reaches everyone. AutoMessage makes server communication deliberate: every stream keeps its own schedule, every message chooses its own audience, and the text arrives in the language each player actually plays in - from one shared core running on Bukkit, Folia, Sponge, Velocity, Fabric and Minestom.

Three announcement surfaces in game at once: a boss bar countdown across the top of the screen, a full-screen title in the middle, and a chat line below it

One announcement set, in game, on one server: a boss bar countdown, a full-screen title and a chat line.

A complete announcement, start to finish

Greet returning veterans three seconds after they join - never on a timer, never the newcomers, and in their own language. Two files say all of it:

# scheduledMessages.yml - who gets it, and when
messages:
  - name: welcome-back
    trigger:
      type: JOIN
      delay: 3s
    rules:
      - type: PLAYTIME
        min: 10h
# lang/en.yml - what it says
announcements:
  welcome-back:
    - chat: "<gray>Welcome back, <aqua>{PLAYER}<gray>."
      sound: "entity.player.levelup MASTER 1.0 1.2"

And this is what the server does with them:

Two players join. Three seconds later the veteran with 240 hours played passes the playtime rule and receives the greeting with a level-up sound; the newcomer with twelve minutes played receives nothing at all.

That is the whole feature set in miniature: an event instead of a clock, an audience instead of everyone, and text that lives beside every other translation.

⚖️ More than a rotating list

The tagline is not marketing. A rotator answers one question - what do I say next? - and sends the answer to everyone, in one language, on one clock.

A plain rotator AutoMessage
Who receives it Everyone online Permission, group, world, online count, playtime - composed with ANY_OF, NONE_OF, NOT
When it fires One global interval A schedule per channel, plus JOIN, FIRST_JOIN and re-arming PLAYER_COUNT triggers
What it looks like A line of chat Chat, action bar, title, subtitle, boss bar and sound, combined in one announcement
What it says One text for everybody The player's own language, with a per-message fallback
Where it runs One server type Bukkit, Folia, Sponge, Velocity, Fabric and Minestom, each a native artifact

The details underneath

🔀 Four rotation strategies

SHUFFLE, SEQUENTIAL, RANDOM or weighted, chosen per channel. Shuffle deals a whole deck before anything repeats.
🧩 Configuration that fits the runtime

A proxy never sees a world rule or a first-join trigger. Options the platform cannot honor are left out of the file, not written and ignored.
👁 Preview before publishing

/automessage view <name> renders the real localized message to you alone, ignoring its audience rules.
↗ Beyond the server

Mirror chat content to a Discord webhook, with player-only placeholders stripped and automatic mentions disabled.
♻ Reload, do not restart

/automessage reload applies schedules, messages and languages in place - including a translation file added while the server was running.
🪶 Lean dispatch path

No database. An empty server short-circuits, placeholders are scanned once per message rather than once per announcement, and everyone reading the same text in the same language is served from a single render.

🎯 Reach exactly who should hear it

Rules compose. Every top-level rule must pass, and ANY_OF, NONE_OF and NOT nest inside them when the audience needs more than an AND.

Twelve players online; a rule requiring the rank.vip permission and at least ten hours of playtime selects five of them, the rest stay dark

The same rules apply to a message sent by an event, so a join greeting restricted to a permission never reaches somebody who lacks it just because they walked through the door.

⚡ Fire on an event, not a clock

A message with a trigger leaves its channel rotation entirely and waits for something to happen.

Three lanes: a channel firing on a five-minute period, a join trigger firing three seconds after a player connects, and a player-count milestone firing once at one hundred online and re-arming only after the count falls below

PLAYER_COUNT is the one worth reading twice: it fires once on the way up and re-arms only after the server drops back below the threshold, so a population hovering on the boundary does not announce the same milestone every time somebody logs in.

🪶 Costs your server almost nothing

Formatting is the expensive part of an announcement. Colors, gradients, hover text and placeholders all have to be parsed before a single character reaches anybody - and the obvious way to do that is once for each player who receives it. Five hundred players online means the same sentence built five hundred times to say one thing, on the main thread, while the rest of the tick waits its turn.

AutoMessage builds it once per language and hands the finished result to everyone reading it.

One announcement reaching 500 players. Formatting once per player means 500 parses of the same text and costs 5.16 ms, about a tenth of a server tick. AutoMessage formats once per language and costs 0.34 ms.

Formatting per player AutoMessage
Parses per announcement One per player, so 500 One per language actually being read
Time on the main thread 5.16 ms 0.34 ms
Share of a 50 ms tick ~10%, every announcement ~0.7%

That is 15× less main-thread time per announcement, measured on the real send path with 500 viewers online - not on a microbenchmark of one function. What remains is handing the finished text to each connection, which no plugin can avoid.

The saving grows with your player count, and it costs you nothing to configure: there is no cache to size, no setting to tune, and no behavior to give up.

It never trades correctness for speed. A message that reads differently for each player - one naming {PLAYER}, or carrying any PlaceholderAPI token - is still built for each of them, because only the expansion behind that token knows whether two players would see the same thing. Grouping applies exactly where the text is genuinely identical, and nowhere else.

Three more places the dispatch path refuses to do needless work:

  • Placeholders are scanned once per message, not once per announcement. Which tokens a message contains is decided by the text, so it is worked out when the files are read and remembered until a reload - rather than re-derived every time the channel fires.
  • An empty server short-circuits. Nobody online means no selection, no formatting and no work.
  • Nothing touches disk while announcing. No database, no player records, no I/O on the hot path.

🌐 One engine. Six runtimes

AutoMessage uses a shared platform-neutral core and a small runtime adapter for each server type. That means the behavior stays familiar while scheduling, viewers, permissions and lifecycle hooks remain native to the platform underneath.

One platform-neutral core connected to six artifacts: Bukkit, Folia, Sponge, Velocity, Fabric and Minestom

Artifact Runtime Supported line Java Platform-specific notes
Bukkit Spigot, Paper, Purpur and other Bukkit-derived servers Minecraft 1.21–26.2 21+ Full feature set; optional PlaceholderAPI integration
Folia Folia Minecraft 1.21–26.2 21+ Dedicated regionized scheduling; optional PlaceholderAPI integration
Sponge Sponge API 17 Minecraft 1.21.10 21+ Full feature set except PlaceholderAPI
Velocity Velocity 3.5.1 proxy Proxy runtime 21+ No world, playtime, first-join or PlaceholderAPI capabilities
Fabric Dedicated Fabric server Minecraft 1.21.11 21+ Server-side mod; Fabric API required; no PlaceholderAPI
Minestom Embedded Minestom library Protocol 1.21.11 25+ Classpath library; no world, playtime, first-join or PlaceholderAPI; permission/group rules require your callback

Note

Java 21 is the bytecode floor for the plugin artifacts, not a promise that every Minecraft server version runs on Java 21. Use the JVM required by your server; Minestom itself requires Java 25.

Each artifact writes a platform-aware configuration. Options the runtime cannot honor are left out instead of being generated and silently ignored.

The same generated file on Paper and on a Velocity proxy: the world rule, playtime rule, first-join trigger, world placeholder, PlaceholderAPI note and playtime example are absent from the proxy file

What about Quilt?

There is no separate Quilt artifact. Quilt can read Fabric metadata, but this branch does not claim a tested Quilt version. Treat the Fabric jar on Quilt as experimental and bring a compatible Fabric API yourself.

🗂 Configuration model

AutoMessage separates when, who and what. Adding a language never requires copying scheduling rules, and changing a channel interval never touches the message text.

flowchart LR
    schedule["config.yml<br/>WHEN<br/>channels · periods · selectors"]
    audience["scheduledMessages.yml<br/>WHO<br/>rules · weights · triggers"]
    content["lang/*.yml<br/>WHAT<br/>localized MiniMessage notices"]
    minecraft["Minecraft<br/>PLAYER OUTPUT"]
    discord["discordWebhook.yml<br/>OPTIONAL MIRROR"]

    schedule --> audience --> content --> minecraft
    content -. "chat text" .-> discord

    classDef config fill:#11182d,stroke:#8b7cf6,color:#eef0ff,stroke-width:1px;
    classDef output fill:#0d2028,stroke:#45d7e8,color:#ecfeff,stroke-width:1px;
    class schedule,audience,content config;
    class minecraft,discord output;
Loading
File Responsibility
config.yml Master switch, fallback language, channels, startup delays, periods and selectors
scheduledMessages.yml Message names, channel assignment, weights, audience rules and event triggers
lang/<code>.yml Localized command replies and the actual MiniMessage announcement payloads
discordWebhook.yml Opt-in Discord webhook, display name and avatar

📥 Get AutoMessage

What you need Where to get it
Current public Bukkit build Modrinth · SpigotMC · Hangar
Release history and changelogs GitHub Releases
Multiplatform artifacts from this branch Build all six from source

Important

Release status: this README documents the local multiplatform branch. The current public listings still represent the earlier Bukkit-focused release line. Until a new GitHub release publishes all six artifacts, build the multiplatform jars with ./gradlew dist.

🚀 Quick start

  1. Get the artifact built for your runtime. Platform jars are intentionally separate.
  2. Put it in your platform's plugins/ or mods/ directory. For Minestom, add the jar to the application classpath and use the builder shown below.
  3. Start the server once. AutoMessage generates annotated configuration plus working example announcements.
  4. Edit config.yml, scheduledMessages.yml and the files inside lang/.
  5. Run /automessage reload, then test a message with /automessage view <name>.

Tip

Start with the generated files. They are not empty templates: every available field is explained next to a working example, and the contents adapt to the platform that created them.

🛠 Build your first announcement

The example below creates an independent event stream, adds one announcement to it, and renders multiple notice types from a single localized payload.

1. Schedule the stream

# config.yml
enabled: true
fallbackLanguage: en

channels:
  - name: default
    enabled: true
    initialDelay: 1m
    period: 5m
    selector: SHUFFLE

  - name: events
    enabled: true
    initialDelay: 30s
    period: 15m
    selector: SEQUENTIAL

2. Register the announcement

# scheduledMessages.yml
messages:
  - name: event-night
    channel: events

3. Design what players receive

# lang/en.yml
announcements:
  event-night:
    - chat:
        - "<dark_gray>[<gold>EVENT<dark_gray>] <gray>The arena opens in <gold>5 minutes<gray>."
        - "<gray>Click <click:run_command:'/warp arena'><hover:show_text:'<yellow>Teleport'><aqua><u>here</u></aqua></hover></click> to join."
      actionbar: "<yellow>Prepare your loadout - the event starts soon"
      title: "<gradient:#ffd166:#ff6b6b><bold>EVENT NIGHT</bold></gradient>"
      subtitle: "<gray>The arena opens in 05:00"
      times: "500ms 3s 500ms"
      bossbar:
        message: "<light_purple>Event countdown"
        duration: 10s
        color: PURPLE
        overlay: PROGRESS
        progress: 0.75
      sound: "block.note_block.pling MASTER 1.0 1.0"

The same announcement on four surfaces at once: a boss bar countdown, a gradient title and subtitle, two chat lines and an action bar above the hotbar

Use only the parts that fit the message. A simple chat announcement can be a single string; a showcase announcement can combine every surface above.

📖 Reference

Announcement channels and selectors

Every channel has its own schedule and independent selector state:

channels:
  - name: tips
    enabled: true
    initialDelay: 1m
    period: 5m
    selector: SHUFFLE

  - name: promotions
    enabled: true
    initialDelay: 2m
    period: 20m
    selector: WEIGHTED
Selector Behavior
SHUFFLE Randomizes the order and shows every message once before anything repeats
SEQUENTIAL Walks straight through the configured list
RANDOM Draws independently; the same message may appear twice in a row
WEIGHTED Draws randomly in proportion to each message's weight

SEQUENTIAL and SHUFFLE keep their position across a normal configuration reload. A weight of 0 parks a message only in a WEIGHTED channel; other selectors do not consult weights.

Audience rules

Top-level rules behave like logical AND. Nest ANY_OF, NONE_OF and NOT when the audience needs more expressive logic.

Rule Matches
PERMISSION Viewers holding a permission node
GROUP Viewers holding group.<name>; this is permission-based, not a direct LuckPerms/Vault lookup
WORLD Viewers in one of the configured worlds
PLAYER_COUNT While the live online count is inside a configured range
PLAYTIME Viewers whose native platform playtime is inside a configured duration range
ANY_OF At least one nested rule
NONE_OF None of the nested rules
NOT The inverse of one nested rule
messages:
  - name: vip-event
    channel: events
    rules:
      - type: ANY_OF
        rules:
          - type: PERMISSION
            permission: rank.vip
          - type: PERMISSION
            permission: rank.staff
      - type: PLAYER_COUNT
        min: 10
        max: 100

Rules still apply to messages sent by an event trigger. Platform-specific rules are generated only where the runtime can supply the required data.

Event triggers

A triggered message leaves its timed channel rotation completely:

messages:
  - name: welcome-back
    trigger:
      type: JOIN
      delay: 3s

  - name: first-join-welcome
    trigger:
      type: FIRST_JOIN
      delay: 3s

  - name: one-hundred-online
    trigger:
      type: PLAYER_COUNT
      threshold: 100

PLAYER_COUNT fires once when the threshold is reached and re-arms only after the online count drops below it. FIRST_JOIN is unavailable on runtimes that do not expose a persistent first-join signal.

[!NOTE] /automessage enable and /automessage disable control scheduled channels. Event-triggered announcements operate independently.

Languages and fallback behavior

AutoMessage ships en, pl and de. Add another language by copying a file to lang/<code>.yml, translating it and running /automessage reload.

For a player using pt_br, AutoMessage looks for:

  1. lang/pt_br.yml
  2. lang/pt.yml
  3. the fallbackLanguage from config.yml

A missing announcement falls back individually, so a new translation does not have to be complete before it is useful.

MiniMessage, placeholders and notification parts

All text uses Kyori MiniMessage, including colors, gradients, decorations, hover text and click actions.

Built-in placeholders:

Player-scoped Server-scoped
{PLAYER} · {DISPLAY_NAME} · {UUID} · {WORLD} {ONLINE} · {MAX_PLAYERS} · {DATE} · {TIME}

On Bukkit and Folia, an installed PlaceholderAPI is detected automatically. Standard %placeholder_name% tokens containing letters, digits and underscores can then be used alongside the built-ins.

Available notification keys:

Key Value
bare string / chat One chat line or a list of lines
actionbar MiniMessage text shown above the hotbar
title / subtitle Title layers; either can be used independently
times Three durations: fade in, stay, fade out
hideTitle Explicitly clear the current title
bossbar Message, duration, color, overlay and optional progress 0.0–1.0
sound Sound key, or key source volume pitch

Need a visual starting point? Try the external EternalCode Notification Generator and adapt the generated notification to the language-file structure above.

Time format

Every duration accepts an explicit unit, and units can be combined:

Unit Meaning Example
ms milliseconds 500ms
s seconds 30s
m minutes 5m
h hours 2h
d days 1d

1m30s is valid. A plain number is interpreted as seconds. Channel periods shorter than 50ms are normalized, and tick-based runtimes ultimately execute on tick boundaries.

Discord webhook mirror

Set enabled: true and provide a Discord webhook URL in discordWebhook.yml to mirror scheduled and triggered announcements.

The integration is deliberately conservative:

  • only chat parts are mirrored;
  • MiniMessage is flattened to plain text;
  • the configured fallback language is used;
  • server placeholders are resolved, while player-scoped values are removed;
  • automatic Discord mentions are disabled;
  • the webhook must use HTTPS and a Discord webhook host/path;
  • rate-limit responses are retried with backoff.

The webhook is opt-in and makes no outbound request while disabled or missing a valid URL. Restart AutoMessage after changing the webhook's enabled state or URL so the connection is rebuilt.

Commands and permissions

Command Permission Description
/automessage enable command.automessage.enable Resume scheduled announcement channels
/automessage disable command.automessage.disable Pause scheduled announcement channels
/automessage reload command.automessage.reload Reload schedules, message definitions and languages
/automessage view <name> command.automessage.view Preview one localized message; player-only, with tab completion
/automessage next command.automessage.next Show what each channel will announce next and how long until it fires, without using it up
/automessage send <channel> command.automessage.send Send that channel's next announcement now; its interval restarts from this moment
/automessage toggle command.automessage.toggle A player turns announcements off for themselves, or back on; remembered across restarts
/automessage stats command.automessage.stats Per channel: how much it has carried, when the last one went out and when the next is due, then the same per message
Embedding AutoMessage in Minestom

Minestom has no plugin directory. Initialize it, place AutoMessage on the classpath and enable the library after Minestom itself is initialized:

MinecraftServer.init();

AutoMessageMinestom autoMessage = AutoMessageMinestom.builder()
        .dataDirectory(Path.of("automessage"))
        .permissions((sender, node) -> myPermissions.check(sender, node)) // optional
        .enable();

// During your server shutdown:
autoMessage.shutdown();

Without a permission callback, AutoMessage uses Minestom's operator level to protect its commands. Permission and group audience rules are then omitted because the runtime cannot answer them accurately.

🔨 Building from source

Building every runtime requires JDK 25. From the repository root, run:

./gradlew dist

The task runs all checks, builds each platform-specific jar and synchronizes the six installable artifacts into build/dist:

  • Bukkit
  • Folia
  • Sponge
  • Velocity
  • Fabric
  • Minestom

If any verification fails, the distribution is not produced. A normal ./gradlew build performs the same project checks but leaves artifacts inside their individual modules.

💛 Support the project

AutoMessage is free, open source and maintained independently.

Found a problem? Want to contribute? Want to support development?
Open a bug report or feature request Read the contribution guide and open a pull request GitHub Sponsors · PayPal

Please follow the project's Code of Conduct in all community spaces. AutoMessage is distributed under the GNU General Public License v3.0.

If AutoMessage makes your server communication better, leave the repository a star.
It helps other server owners discover the project.

Releases

Sponsor this project

Used by

Contributors

Languages