Skip to content
  1. Extras
  2. Sendex
  3. Snippets
  4. Sendex

Sendex

The snippet renders a subscribe or unsubscribe form and handles user actions.

Call it uncached — output depends on login state.

fenom
{'!Sendex' | snippet : ['id' => 1]}
modx
[[!Sendex? &id=`1`]]

Parameters

ParameterDefaultDescription
&idNewsletter ID (sxNewsletter)
&showInactive0Show form for a disabled newsletter
&msgClassactiveCSS class for [[+class]] when [[+message]] is set
&tplSubscribeAuthtpl.Sendex.subscribe.authChunk for logged-in users
&tplSubscribeGuesttpl.Sendex.subscribe.guestChunk for guests
&tplUnsubscribetpl.Sendex.unsubscribeUnsubscribe chunk
&tplActivatetpl.Sendex.activateEmail confirmation chunk

Additional parameters (code only)

These work when passed in the call but do not appear in MODX snippet properties UI:

ParameterDefaultDescription
&confirmEmailsendex_confirm_email (1)Guests must confirm by email
&confirmRateLimitsendex_confirm_rate_limit (0)Interval between confirmation emails (sec)
&csrfProtectsendex_csrf_protect (0)CSRF for POST subscribe/unsubscribe
&loadJs1Load assets/components/sendex/js/web/sendex.js
&widgetKey(empty)Widget key when several forms share one page

Form chunk placeholders

PlaceholderMODXFenom
Newsletter ID[[+id]]{$id}
Name[[+name]]{$name}
Description[[+description]]{$description}
Message[[+message]]{$message}
CSS class[[+class]]{$class}
Error flag[[+error]]{$error}
Widget key[[+widget_key]]{$widget_key}
CSRF token[[+csrf_token]]{$csrf_token}
Subscriber code[[+code]]{$code}

For logged-in users the chunk also receives modUser and Profile fields ([[+username]] / {$username}, [[+email]] / {$email}, etc.).

Subscription flows

Logged-in user

One click → subscribe(userId). Email comes from Profile. If a guest row with the same email exists — user_id is attached.

Guest with confirmation (default)

  1. POST sx_action=subscribe + email
  2. Sendex stores a hash in modDbRegister (/sendex/subscribe/, TTL 30 min)
  3. Sends mail with chunk tplActivate and link sx_action=confirm&hash=...&newsletter_id=...
  4. Link hit → confirmEmail() → subscription with source=confirm

Guest without confirmation

fenom
{'!Sendex' | snippet : ['id' => 1, 'confirmEmail' => 0]}
modx
[[!Sendex? &id=`1` &confirmEmail=`0`]]

Or system setting sendex_confirm_email=No → subscription with source=guest.

Landing page query parameters:

ParameterValue
sx_actionconfirm
hashhash from modDbRegister
newsletter_idnewsletter ID

The snippet on the page handles them automatically; no separate call needed.

Unsubscribe

On the site

Logged-in subscribers see an unsubscribe form. POST sx_action=unsubscribe with subscriber code.

The page must call the Sendex snippet:

fenom
{'!Sendex' | snippet : ['id' => 1]}
modx
[[!Sendex? &id=`1`]]

Query parameters:

ParameterRequiredDescription
sx_actionyesunsubscribe
codeyessxSubscriber.code
newsletter_idnoNewsletter ID; snippet resolves by code if &id differs

Confirm/unsubscribe links omit sendex_widget_key. Keep one snippet without &widgetKey on the landing page.

AJAX

By default the snippet loads sendex.js. Forms with data-sendex-widget, data-sendex-form, data-sendex-message submit via fetch without reload.

Server response:

json
{"success": true, "message": "...", "html": "..."}

AJAX is detected via X-Requested-With: XMLHttpRequest, ajax=1, or sendex_ajax=1.

Disable JS:

fenom
{'!Sendex' | snippet : ['id' => 1, 'loadJs' => 0]}
modx
[[!Sendex? &id=`1` &loadJs=`0`]]

Forms use normal POST with redirect.

Multiple forms on one page

Give each call a unique widgetKey and pass it in hidden sendex_widget_key. Only the matching snippet instance handles the POST.

fenom
{'!Sendex' | snippet : ['id' => 1, 'widgetKey' => 'footer']}
{'!Sendex' | snippet : ['id' => 2, 'widgetKey' => 'sidebar']}
modx
[[!Sendex? &id=`1` &widgetKey=`footer`]]
[[!Sendex? &id=`2` &widgetKey=`sidebar`]]

Letter template

On queue send the newsletter MODX template receives:

ObjectMODXFenom
newsletter[[+newsletter.id]], [[+newsletter.name]], …{$newsletter.id}, {$newsletter.name}, …
subscriber[[+subscriber.email]], [[+subscriber.code]], …{$subscriber.email}, {$subscriber.code}, …
user[[+user.id]], [[+user.username]], …{$user.id}, {$user.username}, …
profile[[+profile.fullname]], [[+profile.email]], …{$profile.fullname}, {$profile.email}, …

Unsubscribe link:

fenom
{set $args = [
  'sx_action' => 'unsubscribe',
  'newsletter_id' => $newsletter.id,
  'code' => $subscriber.code,
]}
<a href="{'site_start' | option | url : ['scheme' => 'full'] : $args}">Unsubscribe</a>
modx
<a href="[[~[[++site_start]]?scheme=`full`&sx_action=`unsubscribe`&newsletter_id=`[[+newsletter.id]]`&code=`[[+subscriber.code]]`]]">
  Unsubscribe
</a>

Custom guest form chunk

fenom
<div class="sendex-widget" data-sendex-widget>
  <p class="sendex-message {$class}" data-sendex-message><b>{$message}</b></p>
  <form action="" method="post" data-sendex-form>
    <input type="hidden" name="sx_action" value="subscribe">
    <input type="hidden" name="newsletter_id" value="{$id}">
    {if $widget_key}
      <input type="hidden" name="sendex_widget_key" value="{$widget_key}">
    {/if}
    {if $csrf_token}
      <input type="hidden" name="sendex_csrf" value="{$csrf_token}">
    {/if}
    <input type="email" name="email" required>
    <button type="submit">Subscribe</button>
  </form>
</div>
modx
<div class="sendex-widget" data-sendex-widget>
  <p class="sendex-message [[+class]]" data-sendex-message><b>[[+message]]</b></p>
  <form action="" method="post" data-sendex-form>
    <input type="hidden" name="sx_action" value="subscribe">
    <input type="hidden" name="newsletter_id" value="[[+id]]">
    [[+widget_key:notempty=`<input type="hidden" name="sendex_widget_key" value="[[+widget_key]]">`]]
    [[+csrf_token:notempty=`<input type="hidden" name="sendex_csrf" value="[[+csrf_token]]">`]]
    <input type="email" name="email" required>
    <button type="submit">Subscribe</button>
  </form>
</div>

Default chunks

ChunkPurpose
tpl.Sendex.subscribe.authForm for logged-in users
tpl.Sendex.subscribe.guestGuest form (email + hidden fields)
tpl.Sendex.unsubscribeUnsubscribe form
tpl.Sendex.activateConfirmation email body ([[+link]], [[+email_body]])

Auth subscribe form

fenom
<div class="sendex-widget" data-sendex-widget>
  <p class="sendex-message {$class}" data-sendex-message><b>{$message}</b></p>
  <form action="" method="post" data-sendex-form>
    <input type="hidden" name="sx_action" value="subscribe">
    <input type="hidden" name="newsletter_id" value="{$id}">
    <button type="submit">Subscribe</button>
  </form>
</div>
modx
<div class="sendex-widget" data-sendex-widget>
  <p class="sendex-message [[+class]]" data-sendex-message><b>[[+message]]</b></p>
  <form action="" method="post" data-sendex-form>
    <input type="hidden" name="sx_action" value="subscribe">
    <input type="hidden" name="newsletter_id" value="[[+id]]">
    <button type="submit">Subscribe</button>
  </form>
</div>

Unsubscribe form

fenom
<div class="sendex-widget" data-sendex-widget>
  <form action="" method="post" data-sendex-form>
    <input type="hidden" name="sx_action" value="unsubscribe">
    <input type="hidden" name="newsletter_id" value="{$id}">
    <input type="hidden" name="code" value="{$code}">
    <button type="submit">Unsubscribe</button>
  </form>
</div>
modx
<div class="sendex-widget" data-sendex-widget>
  <form action="" method="post" data-sendex-form>
    <input type="hidden" name="sx_action" value="unsubscribe">
    <input type="hidden" name="newsletter_id" value="[[+id]]">
    <input type="hidden" name="code" value="[[+code]]">
    <button type="submit">Unsubscribe</button>
  </form>
</div>

Confirmation email (tplActivate)

fenom
<p>Confirm your subscription:</p>
<p><a href="{$link}">{$link}</a></p>
modx
<p>Confirm your subscription:</p>
<p><a href="[[+link]]">[[+link]]</a></p>

Multiple forms and confirm/unsubscribe landing

Two newsletters on one page — each needs its own widgetKey. A separate landing page without widgetKey handles mail links:

fenom
{'!Sendex' | snippet : ['id' => 1, 'widgetKey' => 'promo']}
{'!Sendex' | snippet : ['id' => 2, 'widgetKey' => 'news']}

{# landing /confirm — no widgetKey #}
{'!Sendex' | snippet : ['id' => 1]}
modx
[[!Sendex? &id=`1` &widgetKey=`promo`]]
[[!Sendex? &id=`2` &widgetKey=`news`]]

[[!Sendex? &id=`1`]]

More parameter examples

Inactive newsletter on preview page

fenom
{'!Sendex' | snippet : ['id' => 1, 'showInactive' => 1]}
modx
[[!Sendex? &id=`1` &showInactive=`1`]]

Custom message CSS class

fenom
{'!Sendex' | snippet : ['id' => 1, 'msgClass' => 'alert-success']}
modx
[[!Sendex? &id=`1` &msgClass=`alert-success`]]

Guest → user merge

The Sendex plugin on OnUserActivate and OnUserSave attaches guest rows with the same email to the new modUser. Merge does not run on OnBeforeUserActivate.