Merge pull request #34 from andris9/v1.5

V1.5
This commit is contained in:
Andris Reinman 2016-05-05 17:02:54 +03:00
commit d5222f7b4d
24 changed files with 474 additions and 254 deletions

View file

@ -1,5 +1,10 @@
# Changelog
## 1.5.0 2016-05-05
* Fixed a bug in unsubscribing through the admin interface
* Added individual link click stats
## 1.4.1 2016-05-04
* Added support for RSS templates

View file

@ -177,29 +177,89 @@ module.exports.get = (id, withSegment, callback) => {
let campaign = tools.convertKeys(rows[0]);
if (!campaign.segment || !withSegment) {
return callback(null, campaign);
} else {
segments.get(campaign.segment, (err, segment) => {
if (err || !segment) {
// ignore
return callback(null, campaign);
}
segments.subscribers(segment.id, true, (err, subscribers) => {
if (err || !subscribers) {
segment.subscribers = 0;
} else {
segment.subscribers = subscribers;
let handleSegment = () => {
if (!campaign.segment || !withSegment) {
return callback(null, campaign);
} else {
segments.get(campaign.segment, (err, segment) => {
if (err || !segment) {
// ignore
return callback(null, campaign);
}
campaign.segment = segment;
return callback(null, campaign);
segments.subscribers(segment.id, true, (err, subscribers) => {
if (err || !subscribers) {
segment.subscribers = 0;
} else {
segment.subscribers = subscribers;
}
campaign.segment = segment;
return callback(null, campaign);
});
});
});
}
};
if (!campaign.parent) {
return handleSegment();
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('SELECT `id`, `cid`, `name` FROM campaigns WHERE id=?', [campaign.parent], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return handleSegment();
}
campaign.parent = tools.convertKeys(rows[0]);
return handleSegment();
});
});
});
});
};
module.exports.getLinks = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Campaign ID'));
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'SELECT `id`, `url`, `clicks` FROM links WHERE `campaign`=? LIMIT 1000';
connection.query(query, [id], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return callback(null, []);
}
let links = rows.map(
row => tools.convertKeys(row)
).sort((a, b) => (
a.url.replace(/^https?:\/\/(www.)?/, '').toLowerCase()).localeCompare(b.url.replace(/^https?:\/\/(www.)?/, '').toLowerCase()));
return callback(null, links);
});
});
};
module.exports.create = (campaign, opts, callback) => {
campaign = tools.convertKeys(campaign);

View file

@ -1,7 +1,7 @@
{
"name": "mailtrain",
"private": true,
"version": "1.4.1",
"version": "1.5.0",
"description": "Self hosted email newsletter app",
"main": "index.js",
"scripts": {

BIN
public/images/img01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

BIN
public/images/img02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

BIN
public/images/img03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

BIN
public/images/img04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

BIN
public/images/img05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

BIN
public/images/img06.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
public/images/img07.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
public/images/img08.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
public/images/img09.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View file

@ -271,7 +271,25 @@ router.get('/view/:id', passport.csrfProtection, (req, res) => {
campaign.openRate = campaign.delivered ? Math.round((campaign.opened / campaign.delivered) * 100) : 0;
campaign.clicksRate = campaign.delivered ? Math.round((campaign.clicks / campaign.delivered) * 100) : 0;
res.render('campaigns/view', campaign);
campaigns.getLinks(campaign.id, (err, links) => {
if (err) {
// ignore
}
let index = 0;
campaign.links = (links || []).map(link => {
link.index = ++index;
link.totalPercentage = campaign.delivered ? Math.round(((link.clicks / campaign.delivered) * 100) * 1000) / 1000 : 0;
link.relPercentage = campaign.clicks ? Math.round(((link.clicks / campaign.clicks) * 100) * 1000) / 1000 : 0;
link.short = link.url.replace(/^https?:\/\/(www.)?/i, '');
if (link.short > 63) {
link.short = link.short.substr(0, 60) + '…';
}
return link;
});
campaign.showOverview = true;
res.render('campaigns/view', campaign);
});
});
});
});

View file

@ -378,7 +378,7 @@ router.post('/subscription/unsubscribe', passport.parseForm, passport.csrfProtec
return res.redirect('/lists/view/' + list.id);
}
subscriptions.unsubscribe(list.id, subscription.email, err => {
subscriptions.unsubscribe(list.id, subscription.email, false, err => {
if (err) {
req.flash('danger', err && err.message || err || 'Could not unsubscribe user');
return res.redirect('/lists/subscription/' + list.id + '/edit/' + subscription.cid);

View file

@ -139,8 +139,7 @@ function checkEntries(parent, entries, callback) {
from: parent.from,
address: parent.address,
subject: entry.title || parent.subject,
list: parent.list,
segment: parent.segment,
list: parent.segment ? parent.list + ':' + parent.segment : parent.list,
html
};

View file

@ -1,6 +1,9 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Edit RSS Campaign</li>
</ol>

View file

@ -1,6 +1,9 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Edit Campaign</li>
</ol>

View file

@ -1,6 +1,9 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li class="active">{{name}}</li>
</ol>
@ -16,250 +19,352 @@
<div class="well well-sm">{{{description}}}</div>
{{/if}}
<dl class="dl-horizontal">
<dt>List</dt>
<dd>
{{#if segment}}
<a href="/lists/view/{{list.id}}?segment={{segment.id}}">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="{{#if showOverview}}active{{/if}}"><a href="#overview" aria-controls="overview" role="tab" data-toggle="tab">Overview</a></li>
{{#if links}}
<li role="presentation" class="{{#if showLinks}}active{{/if}}"><a href="#links" aria-controls="links" role="tab" data-toggle="tab">Links</a></li>
{{/if}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane {{#if showOverview}}active{{/if}}" id="overview">
<p></p>
<dl class="dl-horizontal">
<dt>List</dt>
<dd>
{{#if segment}}
<a href="/lists/view/{{list.id}}?segment={{segment.id}}">
{{list.name}}: {{segment.name}}
</a>
{{else}}
<a href="/lists/view/{{list.id}}">
{{else}}
<a href="/lists/view/{{list.id}}">
{{list.name}}
</a>
{{/if}}
</dd>
<dt>List subscribers</dt>
<dd>
{{#if segment}}
{{segment.subscribers}}
{{else}}
{{list.subscribers}}
{{/if}}
</dd>
{{#if isRss}}
<dt>Feed URL</dt>
<dd><a href="{{sourceUrl}}">{{sourceUrl}}</a></dd>
<dt>Last check</dt>
<dd>
{{#if lastCheck}}<span class="datestring" data-date="{{lastCheck}}" title="{{lastCheck}}">{{lastCheck}}</span>{{else}}
Not yet checked{{/if}}
{{#unless isActive}}<span class="text-muted">(feed is only checked if RSS campaign is active)</span>{{/unless}}
</dd>
{{#if checkStatus}}
<dt>RSS status</dt>
<dd>{{checkStatus}}</dd>
{{/if}}
{{/if}}
<dt>Email "from name"</dt>
<dd>{{from}}</dd>
<dt>Email "from" address</dt>
<dd>{{address}}</dd>
<dt>Email "subject line"</dt>
<dd>{{subject}}</dd>
{{#if isNormal}}
{{#unless isIdling}}
<dt>Delivered</dt>
<dd>{{delivered}}</dd>
<dt>Bounced</dt>
<dd>{{bounced}}</dd>
<dt>Complaints</dt>
<dd>{{complained}}</dd>
<dt>Unsubscribed</dt>
<dd>{{unsubscribed}}</dd>
<dt>Opened</dt>
<dd>
<div class="progress">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{openRate}}" aria-valuemin="0" aria-valuemax="100" style="min-width: 2em; width: {{openRate}}%;">
{{openRate}}%
</div>
</div>
{{/if}}
</dd>
<dt>Clicked</dt>
<dt>List subscribers</dt>
<dd>
<div class="progress">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{clicksRate}}" aria-valuemin="0" aria-valuemax="100" style="min-width: 2em; width: {{clicksRate}}%;">
{{clicksRate}}%
</div>
</div>
</dd>
{{/unless}}
{{/if}}
</dl>
{{#if isNormal}}
<div class="panel panel-default">
<div class="panel-body">
{{#if isIdling}}
<form class="form-inline confirm-submit" data-confirm-message="Are you sure? This action would start sending messages to the selected list" method="post" action="/campaigns/send">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<div class="pull-right">
<div class="form-group">
<p class="form-control-static">Delay sending</p>
</div>
<div class="form-group">
<div class="input-group">
<input type="number" class="form-control" name="delay-hours" id="delay-hours" placeholder="0">
<div class="input-group-addon"> hours</div>
</div>
</div>
<div class="form-group">
<div class="input-group">
<input type="number" class="form-control" name="delay-minutes" id="delay-minutes" placeholder="0">
<div class="input-group-addon"> minutes</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-send" aria-hidden="true"></span> Send to
{{#if segment}}
{{segment.subscribers}}
{{else}}
{{list.subscribers}}
{{/if}} subscribers</button>
</form>
{{/if}}
{{#if isSending}}
<!-- Indicate that this page needs refreshing after 20s. -->
<div class="page-refresh" data-interval="20"></div>
{{#if isScheduled}}
<div class="pull-right">
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would reset scheduling" method="post" action="/campaigns/reset">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Cancel</a>
</button>
</form>
</div>
<h4>Sending scheduled <span class="datestring text-info" data-date="{{scheduled}}" title="{{scheduled}}">{{scheduled}}</span></h4>
{{#if segment}}
{{segment.subscribers}}
{{else}}
<div class="pull-right">
<form class="form-horizontal" method="post" action="/campaigns/pause">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
{{list.subscribers}}
{{/if}}
</dd>
<button type="submit" class="btn btn-info"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> Pause</a>
</button>
</form>
</div>
<h4>Sending…</h4>
{{#if isRss}}
<dt>Feed URL</dt>
<dd><a href="{{sourceUrl}}">{{sourceUrl}}</a></dd>
<dt>Last check</dt>
<dd>
{{#if lastCheck}}<span class="datestring" data-date="{{lastCheck}}" title="{{lastCheck}}">{{lastCheck}}</span>{{else}}
Not yet checked{{/if}}
{{#unless isActive}}<span class="text-muted">(activate campaign to start checking feed for new messages)</span>{{/unless}}
</dd>
{{#if checkStatus}}
<dt>RSS status</dt>
<dd>{{checkStatus}}</dd>
{{/if}}
{{/if}}
{{#if isPaused}}
<div class="pull-right">
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/resume">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<button type="submit" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Resume</a>
</button>
</form>
</div>
<h4>Sending paused</h4>
{{#if from}}
<dt>Email "from name"</dt>
<dd>{{from}}</dd>
{{/if}}
{{#if isFinished}}
<div class="pull-right">
<form id="continue-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/send">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<form id="reset-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would reset all stats about current progress" method="post" action="/campaigns/reset">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="continue-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Continue</a>
</button>
<button type="submit" form="reset-sending" class="btn btn-danger"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Reset</a>
</button>
</div>
<h4>All messages sent! Hit "Continue" if you you want to send this campaign to new subscribers</h4>
{{#if address}}
<dt>Email "from" address</dt>
<dd>{{address}}</dd>
{{/if}}
</div>
</div>
{{/if}}
{{#if isRss}}
<div class="panel panel-default">
<div class="panel-body">
{{#if isActive}}
<div class="pull-right">
<form id="inactivate-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would pause sending new entries in RSS feed as email messages to the selected list" method="post" action="/campaigns/inactivate">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="inactivate-sending" class="btn btn-warning"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> Pause</a>
</button>
</div>
Campaign status: <span class="label label-primary">ACTIVE</span>
{{else}}
<div class="pull-right">
<form id="activate-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would start sending new entries in RSS feed as email messages to the selected list" method="post" action="/campaigns/activate">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="activate-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Activate</a>
</button>
</div>
Campaign status: <span class="label label-default">INACTIVE</span>
{{#if subject}}
<dt>Email "subject line"</dt>
<dd>{{subject}}</dd>
{{/if}}
</div>
</div>
<div class="table-responsive">
<div class="well text-info">
If a new entry is found from campaign feed a new subcampaign is created of that entry and it will be listed here
</div>
<table data-topic-url="/campaigns" data-sort-column="4" data-sort-order="desc" class="table table-bordered table-hover data-table-ajax display nowrap" data-topic-args="parent={{id}}" width="100%" data-row-sort="0,1,0,1,1,0">
<thead>
<th class="col-md-1">
#
</th>
<th>
Name
</th>
<th>
Description
</th>
<th>
Status
</th>
<th>
Created
</th>
<th class="col-md-1">
&nbsp;
</th>
</thead>
</table>
{{#if isNormal}}
{{#unless isIdling}}
<dt>Delivered</dt>
<dd>{{delivered}}</dd>
<dt>Bounced</dt>
<dd>{{bounced}}</dd>
<dt>Complaints</dt>
<dd>{{complained}}</dd>
<dt>Unsubscribed</dt>
<dd>{{unsubscribed}}</dd>
<dt>Opened</dt>
<dd>
<div class="progress">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{openRate}}" aria-valuemin="0" aria-valuemax="100" style="min-width: 2em; width: {{openRate}}%;">
{{openRate}}%
</div>
</div>
</dd>
<dt>Clicked</dt>
<dd>
<div class="progress">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{clicksRate}}" aria-valuemin="0" aria-valuemax="100" style="min-width: 2em; width: {{clicksRate}}%;">
{{clicksRate}}%
</div>
</div>
</dd>
{{/unless}}
{{/if}}
</dl>
{{#if isNormal}}
<div class="panel panel-default">
<div class="panel-body">
{{#if isIdling}}
<form class="form-inline confirm-submit" data-confirm-message="Are you sure? This action would start sending messages to the selected list" method="post" action="/campaigns/send">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<div class="pull-right">
<div class="form-group">
<p class="form-control-static">Delay sending</p>
</div>
<div class="form-group">
<div class="input-group">
<input type="number" class="form-control" name="delay-hours" id="delay-hours" placeholder="0">
<div class="input-group-addon"> hours</div>
</div>
</div>
<div class="form-group">
<div class="input-group">
<input type="number" class="form-control" name="delay-minutes" id="delay-minutes" placeholder="0">
<div class="input-group-addon"> minutes</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-send" aria-hidden="true"></span> Send to
{{#if segment}}
{{segment.subscribers}}
{{else}}
{{list.subscribers}}
{{/if}} subscribers</button>
</form>
{{/if}}
{{#if isSending}}
<!-- Indicate that this page needs refreshing after 20s. -->
<div class="page-refresh" data-interval="20"></div>
{{#if isScheduled}}
<div class="pull-right">
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would reset scheduling" method="post" action="/campaigns/reset">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Cancel</a>
</button>
</form>
</div>
<h4>Sending scheduled <span class="datestring text-info" data-date="{{scheduled}}" title="{{scheduled}}">{{scheduled}}</span></h4>
{{else}}
<div class="pull-right">
<form class="form-horizontal" method="post" action="/campaigns/pause">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<button type="submit" class="btn btn-info"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> Pause</a>
</button>
</form>
</div>
<h4>Sending…</h4>
{{/if}}
{{/if}}
{{#if isPaused}}
<div class="pull-right">
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/resume">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<button type="submit" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Resume</a>
</button>
</form>
</div>
<h4>Sending paused</h4>
{{/if}}
{{#if isFinished}}
<div class="pull-right">
<form id="continue-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/send">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<form id="reset-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would reset all stats about current progress" method="post" action="/campaigns/reset">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="continue-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Continue</a>
</button>
<button type="submit" form="reset-sending" class="btn btn-danger"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Reset</a>
</button>
</div>
<h4>All messages sent! Hit "Continue" if you you want to send this campaign to new subscribers</h4>
{{/if}}
</div>
</div>
{{/if}}
{{#if isRss}}
<div class="panel panel-default">
<div class="panel-body">
{{#if isActive}}
<div class="pull-right">
<form id="inactivate-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would pause sending new entries in RSS feed as email messages to the selected list" method="post" action="/campaigns/inactivate">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="inactivate-sending" class="btn btn-warning"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> Pause</a>
</button>
</div>
Campaign status: <span class="label label-primary">ACTIVE</span>
{{else}}
<div class="pull-right">
<form id="activate-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would start sending new entries in RSS feed as email messages to the selected list" method="post" action="/campaigns/activate">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="activate-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Activate</a>
</button>
</div>
Campaign status: <span class="label label-default">INACTIVE</span>
{{/if}}
</div>
</div>
{{/if}}
</div>
{{/if}}
{{#if links}}
<div role="tabpanel" class="tab-pane {{#if showLinks}}active{{/if}}" id="links">
<p></p>
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>
<th class="col-md-1">
#
</th>
<th>
URL
</th>
<th class="col-md-1">
Clicks
</th>
<th class="col-md-1">
% of clicks
</th>
<th class="col-md-1">
% of messages
</th>
</thead>
<tbody>
{{#if links}}
{{#each links}}
<tr>
<td>
{{index}}
</td>
<td>
<a href="{{url}}">{{short}}</a>
</td>
<td>
{{clicks}}
</td>
<td>
{{relPercentage}}
</td>
<td>
{{totalPercentage}}
</td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="5">
No data available in table
</td>
</tr>
{{/if}}
</tbody>
<tfoot>
<tr>
<th></th>
<th>
Aggregated clicks
</th>
<th>
{{clicks}}
</th>
<th>
</th>
<th>
CTR {{clicksRate}}%
</th>
</tr>
</tfoot>
</table>
<p class="text-muted">
Clicks are counted as unique subscribers that clicked on a specific link or on any link (in aggregated view)
</p>
</div>
</div>
{{/if}}
{{#if isRss}}
<div class="table-responsive">
<div class="well text-info">
If a new entry is found from campaign feed a new subcampaign is created of that entry and it will be listed here
</div>
<table data-topic-url="/campaigns" data-sort-column="4" data-sort-order="desc" class="table table-bordered table-hover data-table-ajax display nowrap" data-topic-args="parent={{id}}" width="100%" data-row-sort="0,1,0,1,1,0">
<thead>
<th class="col-md-1">
#
</th>
<th>
Name
</th>
<th>
Description
</th>
<th>
Status
</th>
<th>
Created
</th>
<th class="col-md-1">
&nbsp;
</th>
</thead>
</table>
</div>
{{/if}}

View file

@ -33,6 +33,21 @@
</div>
</div>
<div class="row">
<div class="col-md-4">
<h2>RSS Campaigns</h2>
<p>Setup Mailtrain to track RSS feeds and if a new entry is detected in a feed then Mailtrain auto-generates a new campaign using entry data as message contents and sends it to selected subscribers.</p>
</div>
<div class="col-md-4">
<h2>GPG Encryption</h2>
<p>If a list includes a custom field for a GPG Public Key then subscribers can upload their GPG public keys when signing up or managing preferences. Subscriber that have a key set get all messages from the list in an encrypted form.</p>
</div>
<div class="col-md-4">
<h2>Click stats</h2>
<p>After a campaign is sent, check individual click statistics for every link included in the message.</p>
</div>
</div>
<div class="row">
<div class="col-md-4">
<h2>Open source</h2>
@ -66,19 +81,31 @@
<!-- Wrapper for slides -->
<div class="carousel-inner" role="listbox">
<div class="item active">
<img src="/images/img01_lists.png" alt="Lists">
<img src="/images/img01.png">
</div>
<div class="item">
<img src="/images/img02_list.png" alt="List">
<img src="/images/img02.png">
</div>
<div class="item">
<img src="/images/img03_fields.png" alt="Custom fields">
<img src="/images/img03.png">
</div>
<div class="item">
<img src="/images/img04_segment.png" alt="List segments">
<img src="/images/img04.png">
</div>
<div class="item">
<img src="/images/img05_subscribe.png" alt="List segments">
<img src="/images/img05.png">
</div>
<div class="item">
<img src="/images/img06.png">
</div>
<div class="item">
<img src="/images/img07.png">
</div>
<div class="item">
<img src="/images/img08.png">
</div>
<div class="item">
<img src="/images/img09.png">
</div>
</div>