Skip to content

Releases: NullSablex/email-samp

Release list

v1.0.0

Choose a tag to compare

@NullSablex NullSablex released this 26 Sep 07:18
dbd8d47

First release: 34 natives, 2 callbacks and one binary that loads on SA-MP and on Open Multiplayer, natively as a component or through legacy mode.

Every path below was exercised against a real open.mp server and a real relay, not only in the test suite: mail delivered over STARTTLS to Gmail, templates rendered, an image embedded and a file attached (both arriving byte-identical), accented text from a Windows-1252 gamemode, rate_limit spacing a batch one second apart, and dry_run writing .eml files without touching the network.

The protections were tested the same way, against relays built to break them: one that does not offer STARTTLS, and one presenting a certificate from an untrusted authority. Both are refused, the second one accepted only once tls_ca names the authority. A run with a marked password left no trace of it in the console, in logs/email.log or in the .eml files.

Sending

  • Nothing blocks. Every native returns at once; mail leaves on a pool of four worker threads and reports back through a Pawn callback on a later tick. Nothing touches the network on the main thread.
  • email_send_to(to, subject, body) — a plain-text mail to one address, in one line, on the default account.
  • email_send_file(to, path) — a .tpl or .html file to one address, in one line; the file carries the subject and the wording.
  • email_send(message) — the built message. It consumes the handle once queued; a refused send (closed account, full queue) leaves the handle alive to retry or destroy.
  • Callbacks get the result first, then the values described by the format string. Email::Name(args) declares one in a line, in place of the forward + public pair.
  • email_test opens a session, authenticates and closes it, so a wrong password shows up at startup instead of on the first player's registration.

Messages

  • email_new plus the builder: recipients (To, Cc, Bcc), sender identity, Reply-To, subject, plain and HTML bodies, custom headers.
  • Attachments — email_attach for a file, email_attach_data for text built in the gamemode, email_embed for an image shown inside the HTML as cid:name. Path, size and MIME type are checked on the line that attaches; the contents are read by the worker at send time.
  • Both bodies make a multipart/alternative, so clients that render HTML use it and the rest fall back to the text.

Templates

  • The wording in a file, edited without recompiling: a .tpl with [subject], [text] and [html] sections, or a plain .html file whose <title> becomes the subject.
  • email_set_var takes every kind of value — a template has no arithmetic and no conditions, so every value becomes text whatever it started as. Text is passed as written; a number needs "%d" only because Pawn cannot pass one where a string is expected. The only specifiers that decide anything are %.Nf, for the decimals a float keeps, and %r, which turns the automatic HTML escaping off.
  • Substitution is a single pass: a value is never re-scanned, so one variable cannot be used to read another. An unknown marker stays visible in the mail.
  • Rendered at send time, so email_set_template and email_set_var may be called in any order, and the template fills in only what the message left empty.
  • Cached per file and reloaded when its size or mtime changes. email_force_reload_templates forces it for a deploy that restores timestamps.

Configuration

  • One table of keys, 19 of them, read the same way from every source: a .env through env_samp, the process environment, an SMTP URL, smtp.ini, or the settings string email_connect takes. Keys are case-insensitive and an optional SMTP_ prefix is dropped.
  • An unrecognised key is an error, never a silent no-op: SMTP_PASSWD would otherwise look exactly like "no password configured".
  • email_setup() reads and opens in one line; the first account opened becomes the default, so no call site has to carry a handle. email_setup_env(prefix) does the same from .env keys, and another prefix opens another account.
  • email_connect(host, user, password, settings) for credentials computed at runtime: separate arguments, so a password needs no escaping.

Delivery

Sends go through a scheduler rather than a queue, and no worker ever sleeps holding a thread.

  • email_set_priority with EMAIL_PRIORITY_LOW, NORMAL and HIGH. Each level is a head start, not a separate queue: HIGH goes first, and a LOW that has waited long enough still beats a fresh NORMAL, so nothing starves.
  • rate_limit — messages per minute, evenly spaced, holding back only its own account.
  • retries — a temporary failure is tried again with a doubling delay and the callback runs once, with the final outcome. An unreachable relay pauses the whole account instead (5 s, doubling up to 5 min), so an outage costs one probe per pause rather than one per queued mail.
  • queue_limit — past it a send fails on the calling line with EMAIL_ERROR_QUEUE_FULL. LOW may fill only three quarters of it, so a mailing can never lock out a password reset, and HIGH is never refused by it.
  • Shutdown waits up to 10 seconds for queued mail to leave, then says how many were lost. Nothing is kept on disk.
  • A panicking send does not take the worker with it: it is reported as a failed send and the pool carries on.

Security

  • Header injection is refused, not sanitised — CR, LF or NUL in a subject, display name, address, custom header or attachment name is rejected with EMAIL_ERROR_HEADER_INJECTION, including in a subject a template rendered. Addresses are parsed by an RFC 5321 parser, never matched with a pattern.
  • Template values are HTML-escaped in the [html] part, so a nickname containing <script> arrives as text. %r is the one way to turn that off, for markup the gamemode built itself.
  • Files stay in the server folder — attachments, embedded images and templates must resolve inside the working directory (.. is allowed while the result stays inside), and a symlink or junction anywhere in the path is refused. Checked when the native is called and again right before the worker reads, so swapping a file for a symlink in between does not get past it.
  • TLS is required, not optional: STARTTLS must succeed or the send fails. The trust store is the webpki bundle compiled into the binary, so a private CA needs tls_ca.
  • Plaintext credentials take an explicit opt-in — sending the password unencrypted to a host other than this machine needs allow_plaintext_auth=1; a relay on localhost is allowed with a warning.
  • Caps that turn a leak into an error — 100 recipients, 50 custom headers and 25 MB of attachments per message, 5000 open drafts, 900 bytes per header value.
  • The password reaches no log at all. It goes to the SMTP client and nowhere else: the types that carry it do not even implement Debug. What the console is spared, and logs/email.log keeps, is the rest of the detail — a recipient's address, the relay's own reply. A configuration error names the key and what was expected, never the value.

Diagnostics

  • email_errno / email_error — 0 is the last failure on any account, which is also where a failed email_setup or email_connect lands; an account id reads that account's own.
  • OnEmailError fires once per message, after any retries, so an error there is final. OnEmailSent is its counterpart for mail that leaves. Both reach only the scripts that define them, worked out once when each script loads.
  • email_stats — waiting, sent and failed, per account or for every account with EMAIL_EVERY_ACCOUNT. A connection test passes through the queue but counts as neither, and does not raise OnEmailSent: it is a check, not mail.
  • email_limit — the limits the plugin enforces, so a gamemode splits a mailing by email_limit(EMAIL_LIMIT_RECIPIENTS) instead of hardcoding 100.
  • email_status writes host:port MODE, safe to show in a debug command.
  • dry_run writes each message to logs/dry-run/ as an .eml and sends nothing, for working on a gamemode without spending quota or mailing real players.
  • logs/email.log rotates at 50 MB into gzipped archives; email_log changes the level at runtime.

Platform

  • Accented names survive the trip — Pawn's 8-bit strings are read as the server's code page (charset, any encoding name; windows-1252 by default), instead of being assumed to be UTF-8 and arriving as Jo?o. Writing back, a character the code page cannot represent is reported instead of silently becoming ?.
  • Two includes — <email_samp> in snake_case and <email_samp_omp> in open.mp's Prefix_PascalCase, generated from the first so they cannot drift.
  • No external dependencies — no PHPMailer, no sendmail, no OpenSSL. SMTP and TLS (rustls) are compiled into the binary, for Linux i686 and Windows i686 (MSVC).
  • Documentation in English and Portuguese, and seven runnable examples covering every native.

Full Changelog: https://github.com/NullSablex/email-samp/commits/v1.0.0


Artifacts

  • email_samp.so — Linux i686 (i686-unknown-linux-gnu).
  • email_samp.dll — Windows i686 (i686-pc-windows-msvc).
  • email_samp.inc — Pawn include, identical file for SA-MP and Open Multiplayer.
  • email_samp_omp.inc — the same API under open.mp's Prefix_PascalCase naming (Email_Setup, Email_SendTo). Pure Pawn aliases; pick one include per script, not both.

The same .so / .dll runs on SA-MP and on Open Multiplayer (native component or legacy). Installation paths: see docs/installation.md.