[{"data":1,"prerenderedAt":95},["ShallowReactive",2],{"site":3,"config-slug-site":15,"config-product-en-x3d-mailbox":21,"config-public-x3d-mailbox":90},{"site":4,"contact":10,"seo":13,"payment":14},{"name":5,"host":6,"default_locale":7,"locales":8},"X3D Developer","fivem.x3d-developer.com","th",[7,9],"en",{"discord_invite":11,"email":12,"line":12},"https:\u002F\u002Fdiscord.gg\u002FNA5kPVynFv","",{"google_site_verification":12,"bing_site_verification":12},{"bank_name":12,"account_name":12,"account_no":12,"promptpay_id":12},{"site":16,"contact":18,"seo":19,"payment":20},{"name":5,"host":6,"default_locale":7,"locales":17},[7,9],{"discord_invite":11,"email":12,"line":12},{"google_site_verification":12,"bing_site_verification":12},{"bank_name":12,"account_name":12,"account_no":12,"promptpay_id":12},{"id":22,"slug":23,"name":24,"summary":25,"price_thb":26,"license_mode":27,"cover_key":28,"cover_url":29,"category_slugs":30,"tags":32,"latest_version":51,"latest_released_at":52,"published_at":53,"has_docs":54,"config":55,"rent_price_satang":56,"description_md":57,"docs_md":58,"seo_title":59,"seo_description":60,"media":61,"versions":75},"83c7d7cb-bde4-45e3-a552-d7e377e4dc5d","x3d-mailbox","X3D Mailbox","Free offline mailbox with item and money attachments plus \u002Fredeem codes, no database required — Mail and Safe tabs, any framework through adapter files.",0,"none","public\u002Fproducts\u002Fx3d-mailbox\u002Fcover\u002F743a9e72c1ae8d6d.jpg","\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-mailbox\u002Fcover\u002F743a9e72c1ae8d6d.jpg",[31],"gameplay",[33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50],"esx","free","mailbox","redeem code","standalone","กล่องจดหมาย","ของแนบ","ฟรี","รีดีมโค้ด","ส่งของออฟไลน์","โค้ดรับของ","กรอกโค้ดรับของ","จดหมายออฟไลน์","ส่งของให้คนออฟไลน์","โค้ดแจกของ","แจกของกิจกรรม","ของชดเชย","ไม่ต้องใช้ฐานข้อมูล","1.7.2","2026-10-12T17:22:30.892527Z","2026-09-19T19:05:39.140076Z",true,"public",null,"# An offline mailbox with attachments — free, no database required\n\nA player is offline but still owed an event prize, a compensation item, or a note from an admin — `x3d_mailbox` lets you send a mail with attachments and let them claim it whenever they log back in, through a ready-made Nuxt 4 panel. Works with any framework through editable adapter files.\n\n---\n\n## Features\n\n- [+] Easy setup: tune everything in config, or set it through the web builder\n- [+] Send mail with attachments to offline players, or to everyone\n- [+] Several items plus cash, black money or bank in a single mail\n- [+] Mail expiry with a red countdown warning and a default lifetime\n- [+] Separate Mail and Safe tabs, each with its own unread count\n- [+] \u002Fredeem codes with use caps, one per player and an expiry time\n- [+] A full bag never loses the reward: it waits in the mail\n- [+] Claim all in one click, with double-claim and spam protection\n- [+] Unread mail count shown when a player joins\n- [+] JSON file storage with no database, or switch to MySQL\n- [+] Easy move from other mailboxes: SendMail accepts their format\n- [+] Hook into other scripts through addons\u002F\n\n---\n\n## Attach items and money together\n\n`SendMail`\u002F`SendMailAll` can attach several items and several money accounts (`money`, `bank`, `black_money`, or your framework's own) in a single mail. Set an expiry, set a custom sender name.\n\nMoving from another mailbox? `SendMail` also accepts the format other mailboxes use (`subject`, `cash`\u002F`bank`, `items = {{ name, count }}`, `expiresInDays`), so switching is just a rename of the export.\n\n## Works with any framework\n\nFour files under `addons\u002F` — framework, notify, reward, storage — default to working with ESX out of the box, and fall back to standalone (ace only) with no framework installed. QBCore \u002F ox_lib examples are commented in, ready to swap in.\n\n## No database required\n\nData is stored as a JSON file inside the resource itself, loaded once and flushed every 30 seconds, on player drop, and on a graceful stop or restart. A hard crash mid-session doesn't lose attachments — the real risk runs the other way: a mail claimed inside that last unflushed window (≤30 seconds) has already paid out through the framework, but the on-disk `claimed` flag never caught up, so a restart can let the same mail be opened and paid out again. Need MySQL for a busier server? Set `Config.Storage.Driver = 'oxmysql'` and mail is kept in the `x3d_mailbox` table, created automatically (no `@oxmysql` include needed).\n\n## Can't be claimed twice, a broken reward can't lock a player out\n\nThe claimed flag is written to storage before anything is paid out, so a duplicate claim event can never pay twice. If a misspelled item key makes the reward throw, the mail still gets marked claimed instead of leaving the player stuck retrying forever. Attachment counts are always floored to whole numbers, and the mail cap never deletes a mail that hasn't been claimed yet.\n\n## Built-in redeem codes\n\nHand out welcome, event or compensation codes without another script. A player types `\u002Fredeem \u003Ccode>` or enters it in the mailbox panel, and the reward, items and money alike, is **delivered as mail**, so a full bag never loses it. You can deliver into the Safe tab instead.\n\n- Permanent codes in `Config.Codes`: a server-wide use cap (`maxUses`), one per player (`oncePerPlayer`) and an expiry time (`expires`)\n- Runtime codes from other scripts through exports (`AddCode`, `RemoveCode`, `GetCodeUsage`), for stream or event codes\n- Code usage survives restarts, and `\u002Fredeem` shares the claim throttle and lock, so spamming it still pays out once\n\n## Requirements\n\nNone. No database, no framework, no license. Drop it in and `ensure` it.\n\n**Free** · modify it as you like · just don't resell it or re-upload it under another name.\n","## Installation\n\n1. Unzip and **rename the folder to `x3d_mailbox`** (other scripts call the exports by this name)\n2. Drop it in `resources\u002F`\n3. Add to `server.cfg`:\n\n```\nensure x3d_mailbox\n```\n\nDone — data is stored in `data\u002Fmailbox.json` inside the resource. No SQL to run.\n\nPlayers open the mailbox with `\u002Fmailbox` or the **F7** key (`Config.Key`, empty = no keybind).\n\nIn the panel: two tabs, **Mail** (can expire) and **Safe** (a second channel with its own unread\u002FReceive All count — handy for redeem-code rewards), claim one at a time or **Receive All** in a single click (scoped to whichever tab is open), **delete** a mail once its reward is claimed, see a countdown to expiry, redeem a code from a field right in the panel, and get pinged immediately if a new mail arrives while online.\n\nNo database, no framework, no license required. Works with ESX out of the box, and standalone with no framework installed.\n\n## Framework adapters — 4 adapters under `addons\u002F`\n\n| Folder | Purpose |\n|---|---|\n| `addons\u002Fframework\u002F` | `Framework.IsBusy()` (client) — can the mailbox be opened right now (dead \u002F pause menu); `Framework.Identifier(src)` \u002F `Framework.IsAdmin(src)` (server) — the player's permanent key and admin check |\n| `addons\u002Fnotify\u002F` | `Notify(message, kind)` (client) — notifications |\n| `addons\u002Freward\u002F` | `Reward.CanCarry` \u002F `Reward.GiveItem` \u002F `Reward.GiveMoney` (server) — item\u002Fmoney system |\n| `addons\u002Fstorage\u002F` | `Storage.Get\u002FSet\u002FAll\u002FFlush` (server) — where the data lives (see next section) |\n\nEvery file defaults to working with ESX (it checks `GetResourceState('es_extended')` itself) and carries commented QBCore \u002F ox_lib examples at the bottom to copy over. With no framework at all the mailbox still opens — it just can't deliver attachments until `Reward.GiveItem`\u002F`GiveMoney` is wired up (a console print warns you if it isn't).\n\n## Storage — JSON by default, or switch to MySQL\n\nDefault: a single JSON file (`Config.Storage.File`, default `data\u002Fmailbox.json`), loaded once on resource start, written back every `Config.Storage.FlushSeconds` seconds (default 30) and on player drop \u002F resource stop. An unreadable file is moved to `data\u002Fmailbox.broken.json` and a fresh one is started — the old data is never overwritten.\n\nTo switch to MySQL:\n\n1. Set `Config.Storage.Driver = 'oxmysql'` — the `x3d_mailbox` table is created automatically and no `@oxmysql` include is needed\n2. Restart the resource — nothing else to edit. (Only replace the four `Storage.*` functions in `addons\u002Fstorage\u002Fserver.lua` if you want a custom storage backend.)\n\n## Exports\n\n```lua\n-- target can be a server id (number) or an identifier (string)\nexports['x3d_mailbox']:SendMail(target, {\n    title   = 'Event prize',\n    body    = 'Thanks for joining',\n    sender  = 'Staff',            -- omit = uses the mailbox title\n    box     = 'mail',             -- 'mail' (default, can expire) or 'safe' (the second channel)\n    expires = os.time() + 86400,  -- unix timestamp, omit = never expires\n    items   = { { item = 'bread', count = 5 } },\n    money   = { { account = 'money', amount = 500 } },\n})\n\nexports['x3d_mailbox']:SendMailAll({ title = 'Announcement', body = 'Server restarting tonight' })\n\nexports['x3d_mailbox']:GetMails(src)          --> { {id, title, body, sender, at, expires, read, claimed, box, items, money}, ... }\nexports['x3d_mailbox']:GetUnread(src)         --> number (every box)\nexports['x3d_mailbox']:GetUnread(src, 'safe') --> number (just one box — 'mail' or 'safe')\n```\n\n`GetMails` filters out expired mails automatically, and attaches a Thai `label` from `Config.ItemLabels`\u002F`Config.AccountLabels` to the copies it returns (the stored originals are never touched). `GetUnread` only counts unread, unexpired mails — it returns a plain number, no labels.\n\n`SendMail` also accepts the shape other mailboxes use — `subject`, `cash`\u002F`bank`, `items = {{ name, count }}`, `expiresInDays` — so moving off an old mailbox is a rename of the export.\n\n## Events — server-internal (not a net event)\n\n```lua\nAddEventHandler('x3d_mailbox:onSend', function(identifier, mail) end)\nAddEventHandler('x3d_mailbox:onClaim', function(src, mail, ok) end)\n```\n\n`x3d_mailbox:onSend` fires as soon as a mail is created (from an export or an admin command).\n\n`x3d_mailbox:onClaim` always fires after a claim is attempted — **`mail.claimed` is `true` either way** (the flag is set before payout on purpose, to block a duplicate claim; see Safety below). Check the 3rd argument for the real result — `ok` is `true` only when every attachment was delivered without throwing:\n\n```lua\nAddEventHandler('x3d_mailbox:onClaim', function(src, mail, ok)\n    if not ok then print(('mail %s claimed by %s but a reward failed'):format(mail.id, src)) end\nend)\n```\n\n## Admin commands\n\n`\u002Fmailsend \u003Cserver id or identifier> \u003Ctitle> \u003Cbody…>` and `\u002Fmailsendall \u003Ctitle> \u003Cbody…>` — always callable from the in-game server console; a player needs the `Config.AdminAce` ace (default `x3d_mailbox.admin`) to run them.\n\n## Safety properties, backed by tests\n\n- **Can't be claimed twice** — the `claimed` flag is written to storage before anything is paid out, with no yield between the check and the write; an `InFlight[identifier]` lock also blocks two simultaneous claim events.\n- **A broken reward can't lock a player out** — the payout is wrapped in `pcall`; the mail is still marked claimed even if it throws (a misspelled item key from `\u002Fmailsend` won't leave the player retrying forever). A partial delivery tells the player with its own message instead of staying silent, and never reverts what already landed (that would double-pay it).\n- **Counts are always floored to whole numbers** — fractional or negative attachments never reach the player's inventory.\n- **The player key is always the permanent identifier**, never the server id.\n- **The `Config.MaxMails` cap never deletes an unclaimed, unexpired mail** — it trims claimed or expired mails first, oldest first. A mail that's been read but not claimed (and hasn't expired) still has live attachments and is never deleted.\n- **Receive All is one action, not N** — it pays out through the exact same logic as a single claim, scoped to whichever box tab is open (Mail and Safe never claim each other's mail); a mail that doesn't fit is skipped and stays claimable, never paid twice.\n- **Delete only works on a mail with nothing left to claim** — claim it first (or it never had attachments) — the one exception is an expired mail, which is always deletable since it can no longer be claimed at all — and a client-supplied mail id is only ever looked up inside that player's own list.\n- **`\u002Fredeem` and the redeem net event share the same throttle and lock** — spamming the command can't outrun the rate limit, and the redeem function itself is single-flight per player so two calls can't race each other even if storage starts yielding (e.g. an oxmysql swap).\n- **`Config.Codes` keys are normalised at boot** — a key typed in the wrong case is fixed automatically instead of silently never matching what a player types (with a console warning naming what changed).\n- Rate-limited to 1 call per second per player per action (`open`\u002F`read`\u002F`claim`\u002F`claimAll`\u002F`delete`\u002F`redeem` use separate buckets).\n\n```bash\npip install lupa\npython tools\u002Flua-test\u002Fx3d_mailbox_check.py\n```\n\n## Config (`config.lua`)\n\n| Setting | Purpose |\n|---|---|\n| `Config.Command` \u002F `Config.Key` | Open command (`\u002Fmailbox`) and keybind (`F7`) |\n| `Config.MaxMails` | Mails kept per player |\n| `Config.AdminAce` | Ace required for `\u002Fmailsend` `\u002Fmailsendall` |\n| `Config.Storage.File` \u002F `.FlushSeconds` | JSON file location and flush interval |\n| `Config.ItemLabels` \u002F `Config.AccountLabels` | Thai display labels for the mailbox |\n| `Config.ItemImage` | Path to your inventory's item icons (`{item}` = item name), empty = no icon |\n| `Config.ExpiryWarnHours` | A mail with less than this many hours left shows a red countdown in the panel (default 24) |\n| `Config.CanCarryExport` | Your inventory's capacity-check export — empty = ESX `canCarryItem`. **Seeing \"bag full\" on an empty bag? Set this** — ESX's own weight numbers are stale when another inventory owns them. |\n\n- `Config.DefaultExpireDays` — mail sent without an expiry expires after this many days (the safe never expires)\n- `Config.CheckCarry` — `false` = claim even with a full bag\n- `Config.NotifyUnreadOnJoin` — tell the player their unread count when they load in\n- Item names fall back to the framework's item label when `Config.ItemLabels` has no entry\n\n## Redeem codes\n\nA player types `\u002Fredeem \u003Ccode>`, or types the code into the field right in the mailbox panel, and the reward is **delivered as mail** — a full bag can never lose it, it simply waits in the mailbox.\n\nPermanent codes live in `config.lua`:\n\n```lua\nConfig.Codes = {\n    WELCOME2026 = {\n        maxUses       = 0,        -- 0 = unlimited server-wide\n        oncePerPlayer = true,     -- one per player (default)\n        title         = 'Welcome',\n        items         = { { item = 'bread', count = 2 } },\n        money         = { { account = 'bank', amount = 1000 } },\n    },\n    EVENTONLY = {\n        maxUses = 100,            -- first 100 players only\n        expires = 1790000000,     -- unix time the code stops working\n        box     = 'safe',         -- deliver into the safe instead of mail (optional, default 'mail')\n        money   = { { account = 'money', amount = 5000 } },\n    },\n}\n```\n\nRuntime codes from another resource (gone on restart):\n\n```lua\nexports['x3d_mailbox']:AddCode('EVENT2026', { maxUses = 50, money = { { account = 'bank', amount = 2000 } } })\nexports['x3d_mailbox']:RemoveCode('EVENT2026')\nexports['x3d_mailbox']:GetCodeUsage('EVENT2026')   --> { uses = 12, by = { ['license:xxx'] = true } }\nexports['x3d_mailbox']:Redeem(src, 'EVENT2026')    --> ok, reason\n\nAddEventHandler('x3d_mailbox:onRedeem', function(src, code, def) end)\n```\n\nUsage is stored beside the mail under the system key `__redeem`: it survives a restart, and the broadcast command never writes into it.\n\n## Support\n\nDiscord: discord.gg\u002Fx3d-developer · Web: x3d-developer.com\n","Free FiveM Mailbox & Redeem Code Script","Free FiveM offline mailbox with \u002Fredeem codes: rewards arrive as mail so a full bag never loses them, per-player limits, no database, any framework.",[62,65,67,69,71,73],{"kind":63,"url":64},"image","\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-mailbox\u002Fmedia\u002F5f0e2bffeb5847dd.png",{"kind":63,"url":66},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-mailbox\u002Fmedia\u002Fecae52ada3262923.png",{"kind":63,"url":68},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-mailbox\u002Fmedia\u002Fdfbe3182ca82566c.png",{"kind":63,"url":70},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-mailbox\u002Fmedia\u002F3b10675e2258a5ab.png",{"kind":63,"url":72},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-mailbox\u002Fmedia\u002Fa1d612aa5b9a172d.png",{"kind":63,"url":74},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-mailbox\u002Fmedia\u002F2f18c7395c42734f.png",[76,78,82,86],{"version":51,"released_at":52,"changelog_md":77},"### Changed\n\n- Store images now carry English captions for the English store page",{"version":79,"released_at":80,"changelog_md":81},"1.7.0","2026-10-12T03:25:54.186504Z","### Changed\n\n- UI font is now Kanit (one family for Thai and English), bundled inside the script; the Thai font now really loads in game (FiveM's browser dropped the old font declaration and fell back to a system font)\n### Added\n- `SendMail` also accepts the shape other mailboxes use (`subject`, `cash`\u002F`bank`, `items = {{ name, count }}`, `expiresInDays`) — moving off an old mailbox is a rename of the export\n- `Config.Storage.Driver = 'oxmysql'` keeps mail in table `x3d_mailbox` (created automatically; no `@oxmysql` include needed)\n- `Config.DefaultExpireDays` (mail without an expiry expires after N days; the safe never expires), `Config.CheckCarry` (false = claim even with a full bag), `Config.NotifyUnreadOnJoin` (unread count when a player loads in)\n### Fixed\n- Item names fall back to the framework's item label when `Config.ItemLabels` has no entry\n- Claims were refused on servers whose inventory replaces ESX's (ESX's stale weight vetoed every item); the carry check now asks `Config.CanCarryExport` first everywhere, including inside item delivery",{"version":83,"released_at":84,"changelog_md":85},"1.4.0","2026-09-20T10:38:20.840557Z","### Added\r\n- Safe box: a second mail channel (`box = 'safe'` on any mail, `SendMail`\u002Fredeem codes accept it, defaults to `'mail'`) with its own unread count, its own Receive All (never touches the other box), and its own tab + badge in the panel\r\n- `GetUnread(src, box)` accepts an optional box to scope the count; omitted, it still counts every box like before\r\n\r\n### Fixed\r\n- **Critical:** `\u002Fredeem` bypassed the rate limit entirely (only the net event was throttled) — same-tick command spam could drain a whole `maxUses` pool from one player; the command now shares `doRedeem`'s throttle with the net event, and `Mail.Redeem` itself is single-flight per identifier so a concurrent call (including a direct `exports.x3d_mailbox:Redeem` from another resource) can't race a still-in-flight redeem even once storage starts yielding\r\n- **Critical:** a `Config.Codes` key not already matching the case `\u002Fredeem` compares against (uppercase unless `caseSensitive = true`) was silently unredeemable — keys are now normalised once at boot, with a console warning naming any key that had to change\r\n- Delete now shares the same `InFlight` lock as claim\u002FclaimAll, closing a window where a delete and a claim on the same list could interleave under a storage adapter that doesn't cache in memory (e.g. the documented oxmysql swap)\r\n- A partial delivery (some attachments paid, some failed) now tells the player with its own message (`partial_delivery`) instead of staying silent — the mail is still never reverted, so what already landed is never paid twice\r\n- An expired mail with unclaimed attachments could never be deleted or trimmed by `Config.MaxMails` — its reward is already forfeit once it expires, so it's now deletable and gets reaped by the cap like a claimed mail\r\n- `x3d_mailbox:read` had no throttle bucket — it's rate-limited like every other action now\r\n- `Mail.Add` stored the caller's `items`\u002F`money` tables by reference — `SendMailAll` and a `Config.Codes` entry reused across redemptions could alias into storage; both are now copied on the way in",{"version":87,"released_at":88,"changelog_md":89},"1.0.0","2026-09-19T19:05:37.383308Z","### Added\n- Offline mailbox: mails with title, body, sender, optional expiry, item and money attachments\n- Exports `SendMail(target, mail)`, `SendMailAll(mail)`, `GetMails(src)`, `GetUnread(src)`\n- Server-internal events `x3d_mailbox:onSend(identifier, mail)` and `x3d_mailbox:onClaim(src, mail, ok)`\n- Nuxt 4 panel, `\u002Fmailbox` command and F7 keybind\n- Admin commands `\u002Fmailsend` and `\u002Fmailsendall`, gated by the `x3d_mailbox.admin` ace\n- Claim-once ordering: the claimed flag is written to storage before payout, with an `InFlight` per-identifier lock against simultaneous claims\n- Reward payout wrapped in `pcall` so a broken item\u002Faccount can't leave a player permanently unable to claim\n- Attachment counts floored to whole numbers before every capacity check and payout\n- JSON file storage by default, with a commented oxmysql swap and `install.sql`\n- Four `addons\u002F` adapter files (framework, notify, reward, storage) with ESX defaults and QBCore\u002Fox_lib examples\n- `Config.ItemLabels` \u002F `Config.AccountLabels` for Thai display names",{"slug":23,"version":51,"latest_version":51,"files":91,"options":56},[92],{"path":93,"content":94},"config.lua","Config = {}\r\n\r\n-- เฟรมเวิร์กที่ใช้: 'auto' (ESX ถ้ามี ไม่มีก็ standalone) | 'esx' | 'standalone'\r\nConfig.Framework = 'auto'\r\n\r\nConfig.Locale  = 'th'\r\nConfig.Command = 'mailbox'          -- \u002Fmailbox\r\nConfig.Key     = 'F7'               -- '' = ไม่ผูกปุ่ม\r\nConfig.MaxMails = 50                -- เก็บต่อคนได้กี่ฉบับ (ล้นแล้วทิ้งฉบับที่อ่าน\u002Fรับของแล้วก่อน)\r\nConfig.AdminAce = 'x3d_mailbox.admin'\r\n\r\n-- จดหมายที่เหลือเวลาน้อยกว่านี้ (ชั่วโมง) จะโชว์เตือนสีแดงในพาเนล\r\nConfig.ExpiryWarnHours = 24\r\n\r\n-- จดหมาย (ไม่ใช่ตู้นิรภัย) ที่ผู้ส่งไม่กำหนดวันหมดอายุ จะหมดอายุใน N วัน (0 = ไม่หมดอายุ)\r\n-- Mail (not the safe) sent without an expiry expires after N days (0 = never)\r\nConfig.DefaultExpireDays = 0\r\n\r\n-- true = เช็กว่ากระเป๋ารับไหวก่อนเปิดจดหมาย · false = รับได้เสมอ ให้ inventory จัดการของล้นเอง\r\n-- true = check the bag can carry it before opening a mail · false = always claim, the inventory handles overflow\r\nConfig.CheckCarry = true\r\n\r\n-- แจ้งจำนวนจดหมายที่ยังไม่อ่านตอนเข้าเกม\r\n-- Tell players how many unread mails they have when they load in\r\nConfig.NotifyUnreadOnJoin = true\r\n\r\nConfig.Storage = {\r\n    -- 'file' = ไฟล์ JSON ในโฟลเดอร์สคริปต์ (ไม่ต้องมีฐานข้อมูล) · 'oxmysql' = ตาราง x3d_mailbox (แนะนำเมื่อคนเยอะ)\r\n    -- 'file' = a JSON file in the script folder (no database) · 'oxmysql' = table x3d_mailbox (recommended for busy servers)\r\n    Driver       = 'file',\r\n    File         = 'data\u002Fmailbox.json',\r\n    FlushSeconds = 30,\r\n}\r\n\r\n-- ชื่อไอเทมที่จะโชว์ในกล่องจดหมาย (ไม่ใส่ก็ได้ — ไม่ใส่จะโชว์ชื่อ key ตรง ๆ)\r\nConfig.ItemLabels = {\r\n    -- bread = 'ขนมปัง',\r\n    -- water = 'น้ำดื่ม',\r\n}\r\n\r\nConfig.AccountLabels = {\r\n    money       = 'เงินสด',\r\n    bank        = 'ธนาคาร',\r\n    black_money = 'เงินดำ',\r\n}\r\n\r\n-- รูปไอเทมในกล่องจดหมาย — ใส่ path รูปของ inventory ที่เซิร์ฟใช้ ({item} = ชื่อไอเทม)\r\n-- ปล่อยเป็น '' = ไม่โชว์รูป โชว์แค่ชื่อไอเทม\r\nConfig.ItemImage = ''\r\n-- Config.ItemImage = 'nui:\u002F\u002Fx3d_inventory\u002Fweb\u002Fbuild\u002Fimg\u002Fitems\u002F{item}.png'\r\n-- Config.ItemImage = 'nui:\u002F\u002Fox_inventory\u002Fweb\u002Fimages\u002F{item}.png'\r\n-- Config.ItemImage = 'nui:\u002F\u002Fqb-inventory\u002Fhtml\u002Fimages\u002F{item}.png'\r\n-- Config.ItemImage = 'nui:\u002F\u002Fesx_inventoryhud\u002Fhtml\u002Fimg\u002Fitems\u002F{item}.png'\r\n-- ไม่มีในลิสต์นี้? เปิดโฟลเดอร์รูปไอเทมของ inventory resource ที่ใช้อยู่ แล้วคัดลอก path มาแทน\r\n\r\n-- เช็กว่ากระเป๋ารับของไหวก่อนเปิดจดหมาย — ใส่เฉพาะตอนเซิร์ฟใช้ inventory ของตัวเอง\r\n-- (x3d_inventory\u002Fox_inventory ฯลฯ) ไม่ใช่ ESX ตรง ๆ เพราะเลขน้ำหนักของ ESX เองจะเพี้ยน\u002F\r\n-- ไม่อัปเดตให้ ทำให้จดหมายที่มีของแนบถูกปฏิเสธว่า \"กระเป๋าเต็ม\" ทั้งที่จริงยังว่าง\r\n-- ปล่อยว่าง = เช็กผ่าน ESX canCarryItem ถ้ามี ESX ไม่มีก็ปล่อยผ่าน (allow)\r\nConfig.CanCarryExport = { resource = '', method = '' }\r\n-- Config.CanCarryExport = { resource = 'x3d_inventory', method = 'CanCarryItemSrc' }\r\n-- Config.CanCarryExport = { resource = 'ox_inventory', method = 'CanCarryItem' }\r\n-- ไม่มีในลิสต์นี้? เปิด export ของ inventory resource ที่ใช้อยู่ หาฟังก์ชันเช็กว่ารับของไหวไหม\r\n\r\n-- ═══════════════════════════════════════════════════════════════\r\n--  โค้ดรับของ (redeem code)\r\n--  ผู้เล่นพิมพ์ \u002Fredeem \u003Cโค้ด> แล้วของจะถูก \"ส่งเป็นจดหมาย\" เข้ากล่อง\r\n--  กระเป๋าเต็มก็ไม่มีวันหาย เพราะไปรออยู่ในจดหมาย\r\n-- ═══════════════════════════════════════════════════════════════\r\n\r\nConfig.Redeem = {\r\n    enabled       = true,\r\n    command       = 'redeem',\r\n    caseSensitive = false,      -- false = พิมพ์ตัวเล็กตัวใหญ่ก็ได้\r\n}\r\n\r\n-- โค้ดถาวร — แก้ที่นี่แล้ว restart resource\r\n-- ⚠ คีย์ (เช่น WELCOME2026) จะถูกปรับให้ตรงรูปแบบที่ \u002Fredeem เทียบตอน resource เริ่ม\r\n--   (ตัวพิมพ์ใหญ่ทั้งหมดเมื่อ Config.Redeem.caseSensitive = false) — พิมพ์เล็กพิมพ์ใหญ่ผสมกันได้\r\n--   จะมีคำเตือนใน console ถ้าคีย์ที่ตั้งไว้ไม่ตรงแล้วถูกปรับให้อัตโนมัติ\r\n--   maxUses       จำนวนครั้งที่ใช้ได้ทั้งเซิร์ฟ (0 = ไม่จำกัด)\r\n--   oncePerPlayer true = คนละครั้งเดียว (ค่าเริ่มต้น)\r\n--   expires       เวลา unix ที่โค้ดหมดอายุ (ไม่ใส่ = ไม่หมดอายุ)\r\n--   box           'mail' (ค่าเริ่มต้น, หมดอายุได้) หรือ 'safe' (ตู้ที่สอง แยกกันในพาเนล)\r\n--   title\u002Fbody    ข้อความบนจดหมายที่ผู้เล่นได้รับ (ไม่ใส่ก็มีข้อความมาตรฐานให้)\r\nConfig.Codes = {\r\n    -- WELCOME2026 = {\r\n    --     maxUses       = 0,\r\n    --     oncePerPlayer = true,\r\n    --     title         = 'ยินดีต้อนรับ',\r\n    --     body          = 'ขอบคุณที่เข้ามาเล่นกับเรา',\r\n    --     items         = { { item = 'bread', count = 2 } },\r\n    --     money         = { { account = 'bank', amount = 1000 } },\r\n    -- },\r\n    -- EVENTONLY = {\r\n    --     maxUses = 100,                    -- 100 คนแรกเท่านั้น\r\n    --     expires = 1790000000,             -- เวลา unix\r\n    --     money   = { { account = 'money', amount = 5000 } },\r\n    -- },\r\n}\r\n",1792316316463]