Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='editor' macro='edit title'></div>
<div macro='annotations'></div>
<div class='editor' macro='edit text'></div>
<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser excludeLists'></span></div>
<!--}}}-->
To get started with this blank [[TiddlyWiki]], you'll need to modify the following tiddlers:
* [[SiteTitle]] & [[SiteSubtitle]]: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* [[MainMenu]]: The menu (usually on the left)
* [[DefaultTiddlers]]: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: <<option txtUserName>>
<<importTiddlers>>
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<!--}}}-->
These [[InterfaceOptions]] for customising [[TiddlyWiki]] are saved in your browser

Your username for signing your edits. Write it as a [[WikiWord]] (eg [[JoeBloggs]])

<<option txtUserName>>
<<option chkSaveBackups>> [[SaveBackups]]
<<option chkAutoSave>> [[AutoSave]]
<<option chkRegExpSearch>> [[RegExpSearch]]
<<option chkCaseSensitiveSearch>> [[CaseSensitiveSearch]]
<<option chkAnimate>> [[EnableAnimations]]

----
Also see [[AdvancedOptions]]
<!--{{{-->
<div class='header' role='banner' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
<div class='headerShadow'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
</div>
<div id='mainMenu' role='navigation' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' role='navigation' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' role='complementary' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea' role='main'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}

h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}

.header {background:[[ColorPalette::PrimaryMid]];}
.headerShadow {color:[[ColorPalette::Foreground]];}
.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
.headerForeground {color:[[ColorPalette::Background]];}
.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}

.tabSelected {color:[[ColorPalette::PrimaryDark]];
	background:[[ColorPalette::TertiaryPale]];
	border-left:1px solid [[ColorPalette::TertiaryLight]];
	border-top:1px solid [[ColorPalette::TertiaryLight]];
	border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}

#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}

.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
	border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}

.wizard .notChanged {background:transparent;}
.wizard .changedLocally {background:#80ff80;}
.wizard .changedServer {background:#8080ff;}
.wizard .changedBoth {background:#ff8080;}
.wizard .notFound {background:#ffff80;}
.wizard .putToServer {background:#ff80ff;}
.wizard .gotFromServer {background:#80ffff;}

#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}

.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}

.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}

.tiddler .defaultCommand {font-weight:bold;}

.shadow .title {color:[[ColorPalette::TertiaryDark]];}

.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}

.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}

.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}

.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}

.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}

.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}

.imageLink, #displayArea .imageLink {background:transparent;}

.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}

.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}

.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}

.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}

.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}

.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}
.readOnly {background:[[ColorPalette::TertiaryPale]];}

#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:alpha(opacity=60);}
/*}}}*/
/*{{{*/
* html .tiddler {height:1%;}

body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}

h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}

hr {height:1px;}

a {text-decoration:none;}

dt {font-weight:bold;}

ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}

.txtOptionInput {width:11em;}

#contentWrapper .chkOptionInput {border:0;}

.externalLink {text-decoration:underline;}

.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}

.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}

/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}

#mainMenu .tiddlyLinkExisting,
	#mainMenu .tiddlyLinkNonExisting,
	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}

.header {position:relative;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0 1em 1em; left:0; top:0;}

.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}

#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}

#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 0.3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}

.wizard {padding:0.1em 1em 0 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0; margin:0.4em 0 0.2em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0; margin:0.4em 0 0.2em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0 0; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0;}
.wizardFooter .status {padding:0 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em;}

#messageArea {position:fixed; top:2em; right:0; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em;}
#messageArea a {text-decoration:underline;}

.tiddlerPopupButton {padding:0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em; margin:0;}

.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup .popupMessage {padding:0.4em;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0;}
.popup li.disabled {padding:0.4em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}

.tabset {padding:1em 0 0 0.5em;}
.tab {margin:0 0 0 0.25em; padding:2px;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}

#contentWrapper {display:block;}
#splashScreen {display:none;}

#displayArea {margin:1em 17em 0 14em;}

.toolbar {text-align:right; font-size:.9em;}

.tiddler {padding:1em 1em 0;}

.missing .viewer,.missing .title {font-style:italic;}

.title {font-size:1.6em; font-weight:bold;}

.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}

.tiddler .button {padding:0.2em 0.4em;}

.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}

.footer {font-size:.9em;}
.footer li {display:inline;}

.annotation {padding:0.5em; margin:0.5em;}

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0 0.25em; padding:0 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0 3px 0 3px;}

.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}

.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0; font-size:.9em;}
.editorFooter .button {padding-top:0; padding-bottom:0;}

.fieldsetFix {border:0; padding:0; margin:1px 0px;}

.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0; right:0;}
#backstageButton a {padding:0.1em 0.4em; margin:0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; width:90%; margin-left:3em; padding:1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/
/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
/*}}}*/
/*{{{*/
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none !important;}
#displayArea {margin: 1em 1em 0em;}
noscript {display:none;} /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
}
/*}}}*/
<!--{{{-->
<div class='toolbar' role='navigation' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
A/B split testing is a statistical mechanism for getting your audience to help you pick the best subject line and thus improve open rates. It applies only to open rates because message opening rate is unrelated to message content (because recipients only get to see message content //after// opening a message).

[IMG[images/absplit.gif]]

!The Process
# Create your mailshot as normal.
# In the subject tab, enable A/B split testing, write a second subject line and pick a delay time.
# Schedule your mailshot, bearing in mind that the delay time will affect delivery time for most of your recipients.
Everything else is taken care of for you and happens automatically.
!What Happens
A small portion of your mailing list is randomly selected and split into two halves. The first half receives the usual subject line, the second receives the alternative. These messages are sent as normal, and after they have been sent, your mailshot will be paused (it will be marked as "A/B split paused" on the mailings page). The duration of the pause is determined by the time you specified when creating the mailshot. During this time, your test recipients will have got their messages and treated them as usual: ignoring them, opening them, clicking in them. After the delay period has passed, we count the number of opens associated with each of the two subject lines; whichever one has generated the most opens is declared the winner, and the whole of the rest of the mailshot is sent using that subject line.
!Limitations of split testing
Because of the limits on the effectiveness of split testing (see below), we disable split test options for mailshots sent to lists below 5,000 subscribers, and results obtained from lists below 50,000 should be treated with suspicion! On smaller lists we are forced to use sub-optimal sample sizes - there's not much to be gained by making your entire mailshot a split test!
!The truth about split testing
A/B split testing is often paraded as a vital feature, however it's actually difficult to get meaningful results out of it. Assuming that you write two subject lines that are both as good as you think you can make them (there being no point in writing a deliberately bad one), chances are the difference in response will not be that big, perhaps only 2 or 3 percent. In order to detect that amount of difference, we need a sample size sufficiently large to distinguish a significant result from random variation. Applying statistical theory, to obtain a reading accurate to 2.5% with 95% certainty (the values we use for calculating sample size) in an unlimited population, we need 1,537 samples. Correcting for a finite list of say 20,000, we need 1,427.
Unfortunately that sample is the number of received responses, which corresponds to message openings, not messages sent. A decent list might get a 10% open rate (probably less given we have a limited time window), so we need to send more test messages in order to get the sample rate up to a meaningful level - in the case of our 20k list with a 10% open rate, we need around 14,000 test messages, which is 70% of the list, rendering the whole exercise mostly pointless!
The key thing to note is that the sample size does not increase much as the list size increases, so the bigger your list, the more effective and accurate split testing will be - it will only really start to become effective above around 50,000 subscribers, which renders it unsuitable for many small businesses who simply don't have that many subscribers.
!What about content splits and multivariate testing?
Many advocate taking split testing further, applying it to clicks and thus message content, however given a typical clickthrough rate of 3%, it fails to be statistically significant on lists below about 200,000 subscribers. If your lists are that big, great, but if not, split testing won't help you much. If you go further into multivariate testing, your sample sizes for significance spiral into impossibility. Multivariate testing is excellent and appropriate for web sites (which effectively have a 100% open rate), but it's just not very effective in email.
While any split will usually result in a bias one way or another, below these sample sizes the results are not attributable to anything significant and are indistinguishable from random variation.
To experiment with the statistical values, have a play with [[this calculator|http://www.surveysystem.com/sscalc.htm]].
Our API provides access to various useful back-end functions in Smartmessages, in particular getting a list of mailing lists, subscribing and unsubscribing, and getting info on recipients (including mailing history).
Our API may be overkill for some tasks, particularly for simple synchronisation purposes - our [[Callback|Callbacks]] system may be all you need.
!!What's an API?
An API is an [[Application Programming Interface|http://en.wikipedia.org/wiki/Api]], and presents an access point through which you can write code to talk to Smartmessages behind-the-scenes functions without all that visual HTML stuff to get in the way. This might help you to:
* Build your own subscribe/unsubscribe pages
* Integrate with your CRM system
* Anything else you might want to do!
!!Implementation
The API is an [[RPC|http://en.wikipedia.org/wiki/Remote_Procedure_Call]]-style interface, expecting HTTP POST requests (with a few calls using GET) and providing responses suitable for direct consumption by ~JavaScript ([[JSON|http://en.wikipedia.org/wiki/Json]]), PHP (serialised arrays), XML (everything else), plus simple HTML output to help you debug your code.
The initial focus of our API has been to make basic information available, and provide you with sufficient resources for you to build your own subscribe and unsubscribe pages. If you don't want to go that far, you can [[automate our subscribe and unsubscribe pages|Subscribing]] anyway. There are plenty more functions yet to come!
''Note that all string data, parameters and responses must use the ~UTF-8 character set''.
We reserve the right to withdraw API access, or to place restrictions on the quantity and/or rate of connections and requests.
!!How To Use The API
Firstly you'll need to get API access enabled for your login ID, or we can create a new login for you that has appropriate permissions. Next, go to your account tab inside Smartmessages and copy the API key shown below your account name. If you have access to multiple accounts, they will have different API keys. Then you need to start writing your scripts (using PHP, Java, ASP, Ruby, etc) that call the functions we provide.
!!!Give me the code!
We host our client libraries and example code here and on [[GitHub|https://github.com/Synchro/SmartmessagesClients]]. If you make some worthwhile changes, please fork our code on there and send us a pull request. If you want to suggest an improvement or report a bug please [[create an issue|https://github.com/Synchro/SmartmessagesClients/issues]].
The quickest way to get started is with PHP using our [[API wrapper class|APIExamples]], which implements all features and is ready to go. If you're using another language, you might like to use that code as a basis.
The initial access point is https&#58;&#47;/www.smartmessages.net/api/, but when you log in, you will be provided with an endpoint URL that you should direct subsequent calls to. This allows us future flexibility in how we support the API.
Though we provide JSON output, it's not a good idea to implement access to this API using client-side ~JavaScript, because it's not possible to do so without exposing your login credentials and API key, which would compromise the security of your data.
!!Errors and Feedback
Any function that fails for some reason will return a status value of false, usually accompanied by an error code, and a plain text error message. Some responses contain an additional message field for additional information about the function you just called.
!!!Defined error codes
* ~ERR_INCORRECT_ID_PASS: 1
* ~ERR_UNKNOWN_MISSING_METHOD: 2
* ~ERR_INCORRECT_METHOD_PARAMS: 3
* ~ERR_NO_PERMISSION: 4
* ~ERR_INVALID_EMAIL: 5
* ~ERR_USER_NOT_FOUND: 6
* ~ERR_ALREADY_SUBSCRIBED: 7
* ~ERR_SESSION_EXPIRED: 8
* ~ERR_MISSING_API_KEY: 9
* ~ERR_LOGIN_FAILED_SUSPENDED: 10
* ~ERR_LOGIN_STILL_SUSPENDED: 11
* ~ERR_INVALID_LIST_ID: 12
* ~ERR_SUBSCRIPTION_NOT_FOUND: 13
* ~ERR_INVALID_CUSTOMER_ID: 14
* ~ERR_LIST_UPLOAD_FAILED: 15
* ~ERR_LIST_NOT_YOURS: 16
* ~ERR_ACCOUNT_NOT_FOUND: 18
* ~ERR_NOT_YET_IMPLEMENTED: 19
* ~ERR_INVALID_URL: 20
* ~ERR_UNKNOWN_UPLOAD: 21
* ~ERR_INVALID_NAME: 22
* ~ERR_INVALID_CAMPAIGN_ID: 23
* ~ERR_CAMPAIGN_NOT_YOURS: 24
* ~ERR_TEMPLATE_NOT_FOUND: 25
* ~ERR_INCORRECT_API_KEY: 26
* ~ERR_TEMPLATE_NOT_YOURS: 27
* ~ERR_BAD_URL: 28
* ~ERR_DELETED: 29
* ~ERR_INVALID_MAILSHOT_ID: 30
* ~ERR_NEEDS_UPGRADE: 31
* ~ERR_INTERNAL: 99
Generally you should never receive an error 99 - please report it to us if you do.
!!Output formats
People like to work in different ways, so we provide output in multiple formats which you can choose as best fits your application by specifying which one you want when you log in:
*''php'': This is the result of serialising PHP arrays. If you're working in PHP (as we are), this is by far the easiest format to deal with as all you need to do is [[unserialize($response)|http://www.php.net/manual/en/function.unserialize.php]] to have all the response data in a ready-to use format. This is the default format, and is also what our PHP client class uses.
*''json'': [[JavaScript Object Notation|http://en.wikipedia.org/wiki/Json]] is an increasingly popular markup for web applications due to its compact syntax, ease of parsing and cross-language support.
*''xml'': XML is very popular and flexible, so we provide simple XML output too, though it's a bit more verbose and slower than json or php, so use them if you can.
*''html'': This outputs a simple full HTML page with a debug dump of the response on. This is only of use for debugging.
*''htmlfragment'': Outputs the same data as html format, but without html, head and body tags, useful for debugging if you're making several API calls.
!!Functions
Exact parameter names and response labels are given in italic text. Mandatory parameters are bold. All functions except login require an //''accesskey''// parameter which you will receive in the response to a successful login call, and this parameter is not shown on parameter lists.

!!Connection functions
!!!login
!!!!Parameters:
*//''username''//: the email address that you use to log into Smartmessages
*//''password''//: your password
*//''apikey''//: the key you copied from your account tab.
*//outputformat//: an optional parameter to set the format to one of xml, json, php (the default), html or htmlfragment
!!!!Response
*//accesskey// => string 'abc123'
*//endpoint// => string 'https&#58;&#47;/www.smartmessages.net/api/'
*//status// => boolean true
*//accountname// => string 'Your account name'
*//username// => string 'Your login email address'
*//expires// => integer timestamp
This is the first function you need to call. You should check for the presence of the 'status' value and check that it is not false. You will also be provided with an access key which you will need for all subsequent function calls, and you should use the endpoint value to form the basis of your access ~URLs. The timestamp indicates when your session will expire, though it is extended every time you make a function call.
You need to provide your id and password as well as the API key because the API key is common to all users in an account, but permissions are defined per user.
So that you don't need to expose your own login credentials in source code you may provide to your own customers, we can create additional accounts that have ~API-only access that are unique for your application. Feel free to use your own ID while developing, and request a new separate ID from us when you're ready to deploy.
!!!ping
!!!!Parameters:
*None
!!!!Response:
*//status// => boolean
Does nothing except refresh your access key. Use this if you want to keep your access key valid for an extended period without actually doing anything.
!!!logout
!!!!Parameters:
*None
!!!!Response:
*//status// => boolean
Call this when you've finished using the API. After this call, your access key will no longer work, and you'll have to request a new one using login. If you don't call logout, your key will eventually expire anyway, but it helps us free up resources and thus keep Smartmessages performance high for everyone.
!!Template functions
Note that all imported templates are subject to content filtering for security purposes. Generally this won't make any visible difference, but tags such as scripts and iframes will be removed, along with script-related attributes.
!!!gettemplates
!!!!Parameters:
*//includeglobal//: Boolean; whether to include the standard Smartmessages-supplied templates. Defaults to false.
*//includeinherited//: Boolean; whether to include templates inherited from parent accounts. Defauts to true.
!!!!Response
*//status// => boolean
*//templates// => array
Returns an array of all templates that you have access to.
!!!addtemplate
!!!!Parameters
*//''name''//: The name of the new template, max 100 characters.
*//''html''//: The HTML part of the template.
*//''plain''//: The plain-text half of the template.
*//''subject''//: The default subject line, max 100 characters.
*//description//: A description of the new template, max 255 characters.
*//generateplain// Boolean; whether to create a plain text version from the html version. Defauts to false. The ''plain'' parameter is ignored if this is set.
*//language// The ~ISO-639-1 language that this template is in, defaults to 'en' for English.
*//importimages// Boolean; Whether to import referenced images and rewrite ~URLs to point at the new locations on our servers.
!!!!Response
*//status// => boolean
*//templateid// => integer
*//importcount// => integer (The number of images imported, if any)
Creates a new template. If images are set to import, they are placed in a folder based on the name of the template; any files with the same names will be overwritten. Images that fail to import are skipped and not reported, so it's a good idea to check the template afterwards. Image ~URLs with query strings are skipped (e.g. for third party tracking services).
!!!addtemplatefromurl
!!!!Parameters
*//''name''//: The name of the new template, max 100 characters.
*//''url''//: A URL of the web page you want to import.
*//''subject''//: The default subject line, max 100 characters.
*//description//: A description of the new template, max 255 characters.
*//importcount// Boolean; Whether to import referenced images and rewrite ~URLs to point at the new locations on our servers.
!!!!Response
*//status// => boolean
*//templateid// => integer
*//importedimages// => integer (The number of images imported, if any)
Creates a new template from a URL. This is useful if you develop or generate your templates on an external system. A plain-text version is created automatically. If images are set to import, they are placed in a folder based on the name of the template; any files with the same names will be overwritten. Images that fail to import are skipped and not reported, so it's a good idea to check the template afterwards. Image ~URLs with query strings are skipped (e.g. for third party tracking services).
If the URL fails to respond or returns an error, you'll get an {{{ERR_BAD_URL}}} error code. If you supply a malformed URL or one which doesn't use HTTP or HTTPS, you'll get an {{{ERR_INVALID_URL}}} error code.
!!!updatetemplate
!!!!Parameters
*//''templateid''// integer
*//''name''//: The new name of the template, max 100 characters.
*//''html''//: The HTML part of the template.
*//''plain''//: The plain-text half of the template.
*//''subject''//: The default subject line, max 100 characters.
*//description//: A description of the new template, max 255 characters.
*//generateplain// Boolean; whether to create a plain text version from the html version. Defauts to false. The ''plain'' parameter is ignored if this is set.
*//language// The ~ISO-639-1 language that this template is in, defaults to 'en' for English.
*//importimages// Boolean; Whether to import referenced images and rewrite ~URLs to point at the new locations on our servers.
!!!!Response
*//status// => boolean
*//templateid// => integer
*//importcount// => integer (The number of images imported, if any)
Creates a new template. Note you can only update templates that are in your account - not global or inherited ones. If images are set to import, they are placed in a folder based on the name of the template; any files with the same names will be overwritten. Images that fail to import are skipped and not reported, so it's a good idea to check the template afterwards. Image ~URLs with query strings are skipped (e.g. for third party tracking services).
!!!deletetemplate
!!!!Parameters
*//''templateid''// integer
!!!!Response
*//status// => boolean
Deletes a template. Note you can only delete templates that are in your account - not global or inherited ones.
''Warning: also deletes any mailshots that used this template''.
!!Campaign functions
Campaigns are groups of mailshots organised into folders - don't get them confused with mailshots.
!!!getcampaigns
!!!!Parameters:
*None
!!!!Response
*//status// => boolean
*//campaigns// => array
Returns a list of all campaign ids and names in your account.
!!!addcampaign
!!!!Parameters
*//''name''//: The name of the new campaign, max 100 characters.
!!!!Response
*//status// => boolean
*//campaignid// => integer
Creates a new campaign folder into which you can put new mailshots, or move existing mailshots.
!!!updatecampaign
!!!!Parameters
*//''campaignid''// => integer
*//''name''//: The new name for the campaign, max 100 characters.
!!!!Response
*//status// => boolean
Changes the name of an existing campaign. Note that campaign names don't have to be unique, though it's in your own interests to make them so!
!!!deletecampaign
!!!!Parameters
*//''campaignid''// => integer
!!!!Response
*//status// => boolean
Deletes a campaign.
''Warning: also deletes any mailshots contained in the campaign''.
!!!getcampaignmailshots
!!!!Parameters
*//''campaignid''// => integer
!!!!Response
*//status// => boolean
*//mailshots// => array
Returns an array of useful information about all the mailshots contained by a campaign.
!!Mailshot functions
See also getcampaignmailshots
!!!getmailshot
!!!!Parameters:
*//''mailshotid''//: The integer id of the mailshot you want
!!!!Response:
*//status// => boolean
*//mailshot// => array
**//name// => string
**//subject// => string
**//mailinglistid// => integer The id of the mailing list this mailshot is to
**//templateid// => integer The id of the template being sent
**//campaignid// => integer The id of the campaign this mailshot is stored in
**//status// => string 'unsent', 'complete', 'sending', 'error' etc
**//date_sent// => an ~ISO-8601 date in '~YYYY-MM-DD HH:MM:SS', or a zero date if it's unsent, of the time the mailshot was sent
**//from_address// => string The email address the message is sent from
**//from_name// => string The name the message is sent from
**//replyto// => string The email address replies go to, if different from the from address
**//date_completed// => an ~ISO-8601 date in '~YYYY-MM-DD HH:MM:SS', or a zero date if it's unsent or incomplete, of the time the mailshot completed
**//thumbnail_url// => string A URL of an image of the mailshot as sent (scaled to fit in a 320x320 pixel box)
**//preview_url// => string A URL of a web version of the mailshot as sent
**//recordcount// => integer number of subscribers in the uploaded list (not populated until upload complete)
**//messagecount// => integer Number of messages in this mailshot
**//opencount// => integer number of openings recorded
**//clickcount// => integer number of clicks recorded
Gets information about an existing mailshot.
!!!sendmailshot
!!!!Parameters
*//''templateid''//: The id of the template to send.
*//''listid''//: The id of the mailing list to send to.
*//title//: The title of the new mailshot, max 100 characters. If omitted, will generate a name based on the date.
*//''campaignid''//: The id of the campaign to put the mailshot in.
*//subject//: The subject template to use (allowed syntax is the same as for templates). Will use the template's default subject if omitted.
*//''fromaddr''//: The email address to use as the from address (should be in a domain you have set up DKIM and SPF for).
*//fromname//: The name to use as the sender.
*//replyto//: An address that replies should go to, if different to the from address.
*//when//: When to send the mailshot. Either the word 'now' (the default), or an ~ISO-8601 date in '~YYYY-MM-DD HH:MM:SS' format.
*//elements//: For future expansion, ignore for now.
!!!!Response
*//status// => boolean
*//mailshotid// => integer
Creates a new mailshot and schedules it for sending.
!!!getmailshotclicks
!!!!Parameters:
*//''mailshotid''//: The integer id of the mailshot you want
*//ascsv//: Boolean. Whether to return the result in CSV format
!!!!Response:
*//status// => boolean
*//clicks// => array
**//url// => string The link they clicked on
**//email// => string The address of the recipient that clicked
**//timestamp// => an ~ISO-8601 UTC date in '~YYYY-MM-DD HH:MM:SS'
Gets a list of clicks relating to a particular mailshot. If the {{{ascsv}}} parameter is supplied and true, results will be provided in CSV format, which is smaller, faster and easier to handle (just save it directly to a file) than other formats.
!!!getmailshotopens
!!!!Parameters:
*//''mailshotid''//: The integer id of the mailshot you want
*//ascsv//: Boolean. Whether to return the result in CSV format
!!!!Response:
*//status// => boolean
*//opens// => array
**//email// => string The address of the recipient that clicked
**//timestamp// => an ~ISO-8601 UTC date in '~YYYY-MM-DD HH:MM:SS'
Gets a list of opens relating to a particular mailshot. If the {{{ascsv}}} parameter is supplied and true, results will be provided in CSV format, which is smaller, faster and easier to handle (just save it directly to a file) than other formats.
//Note that we record a single extra open event for recipients that click if they do not have an open logged already. This allows us to avoid the nonsensical situation where someone has apparently clicked whithout opening, which could lead to unique clicks being larger than unique opens.//
!!!getmailshotunsubs
!!!!Parameters:
*//''mailshotid''//: The integer id of the mailshot you want
*//ascsv//: Boolean. Whether to return the result in CSV format
!!!!Response:
*//status// => boolean
*//unsubs// => array
**//email// => string The address of the recipient that clicked
Gets a list of unsubscribes relating to a particular mailshot. If the {{{ascsv}}} parameter is supplied and true, results will be provided in CSV format, which is smaller, faster and easier to handle (just save it directly to a file) than other formats.
!!!getmailshotbounces
!!!!Parameters:
*//''mailshotid''//: The integer id of the mailshot you want
*//''ascsv''//: Boolean. Whether to return the result in CSV format
!!!!Response:
*//status// => boolean
*//bounces// => array
**//email// => string The address of the recipient that clicked
**//code// => integer The bounce reason code
**//type// => string Hard, soft or other
**//reason// => string Text description of the bounce, for example 'Soft bounce - Mailbox full'
**//timestamp// => an ~ISO-8601 UTC date in '~YYYY-MM-DD HH:MM:SS'
Gets a list of bounces relating to a particular mailshot. This is the same information as is downloadable from the contacts page, except that this is for a single mailshot, not a whole list (which might cover multiple mailshots). If the {{{ascsv}}} parameter is supplied and true, results will be provided in CSV format, which is smaller, faster and easier to handle (just save it directly to a file) than other formats.
!!List functions
!!!addlist
!!!!Parameters
*//''name''//: The name of the new list, max 100 characters.
*//description//: A description of the new list, max 255 characters.
*//visible//: Boolean. Whether the new list should be publicly visible, defaults to true.
!!!!Response
*//status// => boolean
*//listidid// => integer
Creates a new mailing list, ready for you to upload subscribers into. List names do not have to be unique.
!!!updatelist
!!!!Parameters
*//''listid''// => integer
*//''name''//: The new name for the list, max 100 characters.
*//''description''//: The new description for the list, max 255 characters.
*//''visible''//: Boolean. Whether the list should be publicly visible
!!!!Response
*//status// => boolean
Changes the properties of an existing list. Note that all parameters are mandatory.
!!!deletelist
!!!!Parameters
*//''listidid''// => integer
!!!!Response
*//status// => boolean
Deletes a list.
''Warning: also deletes any mailshots that have used this list''.
!!!getlists
!!!!Parameters:
*//showall//: (optional) Whether to include all mailing lists, or just those set to 'visible' (the default). 1 for true, 0 for false.
!!!!Response (example data):
*//status// => boolean
*//mailinglists// => array
**341897 => array
***//id// => int 341897
***//name// => string 'Announcements'
***//description// => string 'Occasional information releases'
***//visible// => boolean true
**341886 = array
***//id// => int 341886
***//name// => string 'Newsletter'
***//description// => string 'Regular monthly news, hints & tips'
***//visible// => boolean true
This function returns a list of all the mailing lists that are available in your account that are set to be visible (editable on each mailing list page). The response contains the list ID, its name, and a description of each list, and the array of lists is also indexed by the list ID. Normally the list returned only includes lists that are set to visible, i.e. those that appear on the default landing page; If you add the 'showall' boolean parameter ad set it to 1, the response will include all lists, not just those that are marked as visible. The response includes a 'visible' property for each list which you can use to decide whether to display the list in your own pages.
!!!getlist
!!!!Parameters:
*//''listid''//: The integer id of the list you want
*//ascsv//: Boolean. Whether to return the result in CSV format
!!!!Response:
*//status// => boolean
*//list// => array
** Each entry is a large structure similar to what is returned by //''getuserinfo''//.
Gets a complete list of recipients on a mailing list. If the {{{ascsv}}} parameter is supplied and true, results will be provided in CSV format, which is smaller, faster and easier to handle (just save it directly to a file) than other formats.
''We //strongly// recommend that you use the {{{ascsv}}} option with this function as the response can be extremely large in PHP, JSON or XML formats, extending to hundreds of megabytes for large lists, taking a correspondingly long time to download, and possibly causing memory problems in client code. For this reason, this function defaults to CSV format.''
!!!getlistunsubs
!!!!Parameters
*//''listid''//: The id of the list you want to get unsubscibes from (obtained using the getlists function)
!!!!Response (example data):
*//status// => boolean
*//unsubscribes// => array
**string 'user1@example.com'
**string 'user2@example.com'
**string 'user3@example.com'
Provides a list of all addresses that have unsubscribed from the given list. Note that unsubscribes are retained even if the list is empty, and used to suppress unsubscribed addresses from subsequent uploads. We suggest you occasionally download unsubscribes in order to update your own copies of mailing lists (or make use of our callbacks in order to retain real-time sync).
!!List upload functions
!!!uploadlist
!!!!Parameters:
*//''listid''//: The integer id of the list you want to upload into (obtained using the getlists function)
*//''source''//: A short string describing where this list came from, for example if it's a bought list, the supplier name and order reference. This is used to retain audit trails of your subscribers.
*//definitive//: A boolean value indicating whether this upload is to be considered definitive data that should overwrite any existing data you have for each recipient for the included fields. See MailingLists for more info on the behaviour behind this. Defaults to false.
*//replace//: A boolean value indicating whether anyone already on this list that is not in this new upload should be removed. The default behaviour is to add to the list (i.e. false).
*//firstlinefields//: A boolean value indicating whether the first line of the file contains field names (note that these must match allowed field names). Defaults to false.
*//''file''//: The list file
!!!!Response:
*//status// => boolean
*//uploadid// => integer
This is a complex function used for uploading entire mailing lists in one go. It differs from the rest of our ~APIs in that it requires that your HTTP POST contains a {{{multipart/form-data}}} MIME body. Our reference PHP implementation does this for you and may provide a good basis for you to write code in other languages.
Currently the list file must be in standard [[RFC4180|http://www.rfc-editor.org/rfc/rfc4180.txt]] CSV format - comma delimited, optional double-quote delimiter escaping, double quotes escaped by themselves. This format can be exported from programs like Excel, Filemaker, Access, ~QuickBooks and numerous CRM packages.
The uploading process is asynchronous - this function will return when the physical file transfer is complete, but the actual import of the data takes longer, so you need to monitor its progress and do not send mailshots to the new list until it is complete. This function returns an integer identifier that can be used to query the status of an upload using the getuploadinfo call. You may only submit a single list file per request, and it can be compressed as a zip archive (in which case only the first file inside the archive will be used). There is a limit of 50Mb (uncompressed size).
In the absence of field names in the upload, the importer will fall back to whatever the field list is set to for the account, and if that is not set, to our default field order. The account field order can be set on the settings page, or though the API call 'setimportfields'.
!!!getuploads
!!!!Parameters:
*//''listid''//: The integer id of the list you want upload info for (obtained using the getlists function)
!!!!Response:
*//status// => boolean
*//uploads// => array
**0 => array
***//id// => integer the upload ID
***//status// => string 'pending', 'in progress', 'cancelled', 'complete', 'error' etc
***//progress// => integer (percentage)
***//cancelled// => boolean false (whether the upload has been cancelled)
***//filename// => string 'mylist.csv'
***//recordcount// => integer number of subscribers in the uploaded list (not populated until upload complete)
***//badcount// => integer number of invalid addresses in the uploaded list (not populated until upload complete)
***//groupcount// => integer number of addresses that look like group addresses in the uploaded list (not populated until upload complete)
***//uploaddate// => date and time (~ISO-8601 format) the upload completed (not populated until upload complete)
This function gets a history of all uploads, including recent ones. Typically you should call this function when monitoring an upload's progress in preference to the getuploadinfo function as it's faster and returns less information that's not relevant to the upload progress (even though it returns info on more than one upload).
!!!getuploadinfo
!!!!Parameters:
*//''listid''//: The integer id of the list you want upload info for (obtained using the getlists function)
*//''uploadid''//: The integer id of the particular upload you want info for (obtained using the getuploads or uploadlist functions)
!!!!Response:
*//status// => boolean
*//upload// => array
**//id// => integer The upload ID
**//status// => string 'pending', 'in progress', 'cancelled', 'complete', 'error' etc
**//progress// => integer (percentage)
**//cancelled// => boolean false (whether the upload has been cancelled)
**//filename// => string 'mylist.txt'
**//recordcount// => integer number of subscribers in the uploaded list (not populated until upload complete)
**//badcount// => integer number of invalid addresses in the uploaded list (not populated until upload complete)
**//groupcount// => integer number of addresses that look like group addresses in the uploaded list (not populated until upload complete)
**//uploaddate// => date and time (~ISO-8601 format) the upload completed (not populated until upload complete)
**//badaddresses// => array The array of invalid email addresses, if any. 
*** 0 => 'invalid@example.'
**//groupaddresses// => array The array of group email addresses, if any. 
*** 0 => 'sales@example.com'
*** 1 => 'customerservices@example.com'
**//definitive// => boolean true (whether the upload was uploaded in definitive mode - see the uploadlist function)
**//replace// => boolean true (whether the upload was uploaded in replace mode - see the uploadlist function)
**//firstlinefields// => boolean true (whether the upload had field names in its first line - see the uploadlist function)
**//contactcount// => integer number of entirely new contacts in this list (not populated until upload complete)
**//subcount// => integer number of new subscriptions in this list (not populated until upload complete)
**//suppressunsubcount// => integer number of unsubscribes suppressed from this upload (not populated until upload complete)
**//suppressspamcount// => integer number of spam reporters suppressed from this upload (not populated until upload complete)
**//suppressbouncecount// => integer number of known bouncing addresses suppressed from this upload (not populated until upload complete)
**//uploadbyid//: integer The ID of the user that did the upload - can be fed into the getuserinfo function
**//source//: A short string describing where this list came from, see uploadlist for details
!!!cancelupload
!!!!Parameters:
*//''listid''//: The integer id of the list the upload is in (obtained using the getlists function)
*//''uploadid''//: The integer id of the particular upload you want to cancel (obtained using the getuploads or uploadlist functions)
!!!!Response:
*//status// => boolean
If an upload has 'pending' or 'in progress' status, it can be cancelled, and none of the addresses on it will appear on the list and none of the field data changed by it will be applied.
!!Subscriber functions
!!!subscribe
!!!!Parameters
*//''address''//: The email address you want to subscribe
*//''listid''//: The id of the list you want to subscribe to (obtained using the getlists function)
*//name//: Optionally, the name you want to give to this subscriber. Goes into the 'Dear' field within Smartmessages, usually used when addressing the recipient, e.g. 'John', 'Dr Who', 'Mr President', 'Gnasher' etc.
*//title//: Optionally, the title name you want to give to this subscriber.
*//firstname//: Optionally, the subscriber's first name.
*//lastname//: Optionally, the subscriber's last name (surname).
*//companyname//: Optionally, the subscriber's company name.
!!!!Response:
*//status// => boolean
*//msg// => string optional message
Doesn't need much explanation! If your subscription policy is confirmed or double opt-in (which it is by default), your recipients will receive a confirmation message which they may need to respond to before they receive messages from this list. If you subscribe someone who is already on the list, you will not get an error, but you will receive an additional //msg// property in the response saying so. The optional naming fields provide an easy way to add someone to a list and record their name in one go without having to call setuserinfo later, so it's ideal as something to work behind a signup form on your web site.
!!!unsubscribe
!!!!Parameters
*//''address''//: The email address you want to unsubscribe
*//''listid''//: The id of the list you want to unsubscribe from (obtained using the getlists or getuserinfo functions)
!!!!Response
*//status// => boolean
If you try to unsubscribe someone from a list that is not on it, you will receive an error response. Unsubscribes take effect immediately.
!!!deletesubscription
!!!!Parameters
*//''address''//: The email address you want to delete from a list
*//''listid''//: The id of the list you want to delete the subscriber from (obtained using the getlists or getuserinfo functions)
!!!!Response
*//status// => boolean
This works just like the unsubscribe call but without the associated semantics, so there will be no unsubscribe notification, no global unsubscribe action, no addition to suppressions etc, the subscriber is simply removed from the specified list with no other side-effects.
If you try to delete someone from a list that is not on it, you will receive an error response. Deletes take effect immediately.
!!!getuserinfo
!!!!Parameters
*//''address''//: The email address you want to get info on.
!!!!Response (example data):
*//status// => boolean true
*//userinfo// => array
**//email// => string 'user@example.com'
**//ownerdata// => string 'abc123'
**//title// => string 'Mr'
**//initials// => string 'FJ'
**//jobtitle// => string 'Marketing Director'
**//firstname// => string 'Finbar'
**//lastname// => string 'Jones'
**//dear// => string 'Mr Jones'
**//companyname// => string 'Example Marketing'
**//address1// => string 'Example House'
**//address2// => string 'Noodle Road'
**//address3// => string
**//county// => string
**//posttown// => string 'London'
**//postcode// => string '~SE1 5OK'
**//country// => string 'GB'
**//phone// => string '020 8123456'
**//fax// => string 
**//mobile// => string 
**//dob// => string '1971-05-27'
**//sex// => string 'm'
**//url// => string 'http&#58;&#47;/www.example.com/'
**//timezone// => string 'Europe/London'
**//custom1// => string 'custom value 1'
**//custom2// => string 'custom value 2'
**...
**//custom31// => string 'custom value 31'
**//custom32// => string 'custom value 32'
**//subscriptions// => array
***0 => array
****//listid// => int 341897
****//name// => string 'Announcements'
****//description// => string 'Occasional information releases'
****//visible// => boolean true
***1 => array
****//listid// => int 341886
****//name// => string 'Newsletter'
****//description// => string 'Regular monthly news, hints & tips'
****//visible// => boolean true
***2 => array
****//listid// => int 341889
****//name// => string 'Test list'
****//description// => string
****//visible// => boolean false
**//mailshots// => array
***0 => array
****//mailshotid// => int 33988
****//name// => string 'First test'
****//date_sent// => string '2007-07-06 14:20:00'
****//listid// => int 341889
****//previewurl// => string 'http&#58;&#47;/www.smartmessages.net/web/3939_33988_'
***1 => array
****//mailshotid// => int 33989
****//name// => string 'Example July Newsletter'
****//date_sent// => string '2007-07-06 15:20:24'
****//listid// => int 341886
****//previewurl// => string 'http&#58;&#47;/www.smartmessages.net/web/3939_33989_'
This returns lots of useful information about the recipient. You can see all their personal data, their current subscriptions (including those to invisible lists), and a list of all the messages they have been sent (along with preview ~URLs so you can see a message like the one they were sent). The list of personal data fields is subject to change, but we'll try to only add fields rather than take them away.
!!!setuserinfo
!!!!Parameters
*//''address''//: The email address you want to get info on.
*//''userinfo''//: An array of properties to set
!!!!Response:
*//status// => boolean
Provides a counterpart to the getuserinfo function for setting personal data, perhaps to sync with your CRM system, or provide storage back-end for a custom form. A user's properties have the same names as those returned from getuserinfo, but you need to wrap them in an HTML array style query string - there is a function in the [[example code|APIExamples]] to do that for you. Note that you CANNOT change a user's email address. If you need to change an email address, unsubscribe the current address and re-subscribe under the new address.

Some fields have input constraints:
*Only ~UTF-8 encoding is supported
*Most address fields are limited to 100 characters
*Phone numbers are limited to 20 characters
*Dates must be in ~ISO-8601 ~YYYY-MM-DD format
*Country requires a 2-character ~ISO-3166 code (Notably the UK is 'GB' in this scheme)
*Language requires a 2-character ~ISO-639-1 code
*Sex may be 'm', 'f', or 'unknown' (the default)
*Only custom fields are allowed to contain line breaks
*Custom fields are limited to 255 characters

Fields that are not set are not changed, fields that are set but empty will be cleared.

The meaning of custom fields is entirely up to you, and you are limited to 32 custom fields.
!!Account-level functions
!!!getspamreporters
!!!!Parameters
*None
!!!!Response (example data):
*//status// => boolean
*//spamreporters// => array
**string 'user1@example.com'
**string 'user2@example.com'
**string 'user3@example.com'
Provides a list of addresses that have reported emails from you as spam, at ~ISPs that we receive such reports from. Note that these are treated account-wide - users are unsubscribed from all your lists and prevented form being added to any list in future - it's just not worth trying to retain such recipients.
!!!getcallbackurl
!!!!Parameters:
*None
!!!!Response:
*//status// => boolean
*//url// => text
Retrieve the callback URL for your account.
!!!setcallbackurl
!!!!Parameters:
*//''url''//: text ~'http://www.example.com/callback.php' (put your callback URL here)
!!!!Response:
*//status// => boolean
Set the callback URL for your account. This must be a complete, valid URL using either HTTP or HTTPS protocol. Setting the URL to an empty string will disable callbacks; If callbacks have been disabled on your account (e.g. if your callback handler breaks), setting the URL to a valid, non-empty value (including to its existing value) will re-enable callbacks. If the URL you provide is invalid, the existing value will not be changed.
!!!getfieldorder
!!!!Parameters:
*None
!!!!Response (default fields shown)
*//status// => boolean
*//fields// => array
**0 => emailaddress
**1 => ownerdata
**2 => title
**3 => initials
**4 => jobtitle
**5 => firstname
**6 => lastname
**7 => dear
**8 => companyname
**9 => address1
**10 => address2
**11 => address3
**12 => posttown
**13 => county
**14 => postcode
**15 => country
**16 => phone
**17 => fax
**18 => mobile
**19 => dob
**20 => sex
**21 => custom1
**...
**32 => custom32
**33 => preferred_format
**34 => preferred_format_sms
**35 => preferred_language
**36 => url
**37 => crm_id
This function retrieves the current default import field order for your account. If you have not set a field import order, then you will get back the default order (as shown). The same functionality is available on the account settings page.
!!!setfieldorder
!!!!Parameters:
*//fields// => array (example shown)
**0 => emailaddress
**1 => firstname
**2 => lastname
!!!!Response
*//status// => boolean
Using this function you can set the default import order for subsequent uploads. This is particularly useful if you frequently upload lists with new subscriber data as you can set up Smartmessages to match the export format of whatever database or spreadsheet you're using. You simply provide an array of the fields you want in the order you want. The fields you specify must be a subset of our default set (see the getfieldorder function for what they are); Fields cannot appear more than once; If the list you provide contains field names we don't know, the whole request will be ignored and will be rejected with a false status value. The field list ''must'' contain 'emailaddress' - a list is not any use without it! The same functionality is available on the account settings page.
!!Utility functions
!!!validateaddress
!!!!Parameters:
*//''address''//: string email address
!!!!Response:
*//status// => boolean
*//valid// => boolean
A utility function to assess whether an email address is valid. This is the same check as we use internally to spot bad addresses during uploads. Just submit an address and check the value of the 'valid' response property. [[Read more|Email Address Validation]] about address validation.
We provide a client wrapper class and sample code for our [[API]] in PHP which you can find [[on GitHub|https://github.com/Synchro/SmartmessagesClients]]. You will need to substitute your own login ID, password and API key in the example script. There's also a .NET library on there. If you write a wrapper for other languages, we'd love to make it available to other users.

If you have any ideas or improvements you'd like to see in our API, please [[create an issue|https://github.com/Synchro/SmartmessagesClients/issues]] on ~GitHub, or create a fork and submit a pull request.
!No sending limit
For most self-service accounts, there is no limit on the number of messages you can send to your recipients, and it won't cost you any more to email them more often. This doesn't apply if you have negotiated a per-message rate.
!Contact limits
Where we do impose limits is on the number of contacts that you can hold in your account at any one time. This may be a larger number than are on your current lists because we need to hold data on addresses that have bounced, reported spam, unsubscribed or are on suppression lists. If you go over your account limit, we will not prevent you from uploading more, nor block subscriptions to your lists, but you will not be able to send messages to your lists until you have increased your subscription level to cover your contacts - see [[Payment]] for how to do that.
!!Removing unused contacts
If you're really concerned about the number on non-subscribed contacts you have (for example if you uploaded a new 10k list and then deleted it, you would be left with 10k non-subscribed contacts eating up your allowance), we can delete them for you, though we generally won't delete those that have asked to be unsubscribed or suppressed (as opposed to those you have deleted from lists) as it would make it impossible to fulfil our data protection obligations. If after this you're still over quota, you will have to [[upgrade your subscription|Payment]].
Under the ''Settings'' tab you can:

Change your password & manage your time zone

[IMG[images/personal.gif]]

Edit your company details:

[IMG[images/account.gif]]

Attach in Google Analytics:

[IMG[images/google.gif]]

Set up your domain authentication to get better delivery:

[IMG[images/domain.gif]]

Set up your field order so that uploads are in your own format:

[IMG[images/fieldorder.gif]]
Smartmessages does not have auto-responders as they are more appropriately handled by CRM systems.

You can achieve some of the functionality that autoresponders are commonly used for using [[Continuous mailshots]].
There are several ways that you can automate interactions with Smartmessages:
*Calling our standard [[subscribe|Subscribing]] and [[unsubscribe|Unsubscribes]] forms
*Using our [[API]]
*Using [[callbacks|Callbacks]]
*Using [[Continuous mailshots]]
Please read those sections for more details
A bounce or bounceback is a message received from a mail server describing a failed delivery attempt. A typical example would be if you sent jack@example.com an email, but there's nobody called jack at example.com, so the mail server at example.com sends a message back to a special address we put in each message describing why the message couldn't be delivered. We handle all the bounces that your mailings generate, analyse them, take necessary action, and provide reports on them.

Bounces are a thorny problem for any mailing list system. Mail servers are unpredictable and very often badly configured, and you're likely to interact with several thousand different ones on a reasonable size mailing.

People often talk of 'hard' and 'soft' bounces without really knowing what they mean. In fact, hard bounces are the only ones of any real interest, and even then they are not necessarily 'hard' at all - it's all much less black and white that you might have been led to believe. Generally a hard bounce is one that is negative and likely to be permanently true, for example a reply which says 'this user does not exist'. A soft bounce is pretty much anything else, even those that describe themselves as 'permanent', 'fatal' or other such strong terms - for example a user's mailbox being full is classed as a permanent error, even though they could empty it at any moment.

When we receive a bounce message, we try to interpret what it says as accurately as possible, but it's not easy. Though there are some common standards for such messages, adherence to the rules is pretty bad. Our mail servers use a bounce processing system based on [[BoogieTools BounceStudio|http://www.boogietools.com/]]. We return the same codes we receive from their handling of bounces in the downloadable bounce reports we provide. Here's an overview of the codes:

|!Bounce Type|!Description|
|0|NON BOUNCE|
|10|HARD BOUNCE|
|20|SOFT BOUNCE - General|
|21|SOFT BOUNCE - Dns Failure|
|22|SOFT BOUNCE - Mailbox Full|
|23|SOFT BOUNCE - Message Size Too Large|
|30|BOUNCE WITH NO EMAIL ADDRESS|
|40|GENERAL BOUNCE|
|50|MAIL BLOCK - General|
|51|MAIL BLOCK - Known Spammer|
|52|MAIL BLOCK - Spam Detected|
|53|MAIL BLOCK - Attachment Detected|
|54|MAIL BLOCK - Relay Denied|
|60|AUTO REPLY|
|70|TRANSIENT BOUNCE|
|80|SUBSCRIBE REQUEST|
|90|UNSUBSCRIBE REQUEST|
|100|CHALLENGE RESPONSE|

Most of these broad categories are easy enough to understand. A non-bounce is a message that's ended up at our bounce return address that isn't a bounce, for example if a recipient sent a reply to the bounce address instead of from or replyto addresses because their mail program was misconfigured, or because a spam generator guessed lucky and created a legitimate bounce address for a spam message. For full details on what these codes mean, [[read their documentation|http://www.boogietools.com/Products/Linux/BounceStudioAPI/help/files/BounceTypes.html]].

Bounces can take time - so just because your mailshot has all been sent and you have a nice low bounce count, don't be surprised when you find that it increases later on. We've had some mail servers send us bounces 6 months after sending!

We use [[VERP|http://en.wikipedia.org/wiki/Variable_envelope_return_path]] addressing for our return path (where bounces go), so we can cope with some server misconfigurations - for example, Microsoft Exchange sometimes sends bounces that don't actually say who the original message was sent to (helpful, huh?!), but because of our addressing system, we can still figure out who it was.

Sometimes we get a bounce that says 'Uh, something didn't work' with no indication of what or why it failed, and though we try, there's not a lot we can do about such messages - it may be that the user doesn't exist, or their mailbox is full, or their mail server is just rubbish (it happens!). Sometimes mail servers just go nuts - we once had a major ISP's mail server send us 30,000 bounce messages in reply to a single message! There's only so much we can do - and all mailing list providers are in the same boat here.
!!!Greylisting
Some mail servers use [[greylisting|http://en.wikipedia.org/wiki/Greylisting]] in an often successful attempt to fool the millions of zombie ~PCs responsible for most spam into not sending them messages. This involves initially rejecting an incoming message, but allowing it to be received later on. It relies on the idea that the minimal mail senders in such programs will never get around to trying again and don't care if some of their millions of messages are not delivered. Because we run well-behaved mail servers, we have full support for this system - but be aware that it can mean that messages take longer to be delivered.
!!Automatic unsubscribe of bounces
We automatically unsubscribe consistently bouncing addresses after approximately three bounces. It's approximate because we adapt according to the historical behaviour of each address, for example if a hard bounce happens in a sequence of otherwise successful deliveries to an address, it will be ignored and future deiveries won't be affected. There is a major exception to this: Yahoo! domains (and other domains they handle, such as btinternet.com) are unsubscribed after a single hard bounce; it's a requirement of their terms of service for deliveries, and repeatedly attempting deliveries to their domains just results in delivery penalties.

Because these bounces are not dependent on content or source, we maintain them globally, so that if an address is bouncing for one of our other customers, it will be suppressed from your lists when you upload them, which helps improve your deliverability by not sending to known-bad addresses - //even on your very first send//!

We auto-unsubscribe bounces that are reported as 'unknown user', 'unknown domain', 'mailbox full' or 'bad address syntax'. It's useful to remove 'mailbox full' addresses as these days mailboxes are rarely full for real users, so they are likely to be defunct accounts that are accumulating junk, especially if they give that error consistently over several delivery attempts.
Smartmessages can display your branding in place of ours on several pages your subscribers may visit. The options controlling this are on the branding tab, which you'll find under the account tab.

If you set these options, we will use your branding on the following pages:
* Your landing page
* All subscribe pages
* All unsubscribe pages
* Double-opt-in verification page
* Your privacy policy page
* Branding test page
* Subscriber options pages (shown to subscribers when taking up their right to access their own data)
!Account logo
We can make use of your own logo in numerous places around our web site and in some email messages. Create a logo image with the following criteria:
* Must fit into 256 pixels wide by 128 pixels tall.
* Images bigger than this will be scaled down to fit.
* Images smaller than this will ''not'' be scaled up.
* Be in PNG format (other formats will be converted into PNG, so you can upload other formats if you like).
* PNG transparency will be preserved (and is highly recommended).
* Must be readable on a light background - white text on a transparent background will be invisible if we display it on a white page.
Here's an example; our own logo:

[IMG[http://www.smartmessages.net/logo/2]]

Your logo will be used in the following locations:
* In the header of all notification messages sent to your subscribers
* On the home page.
* On the home page of accounts that you refer to us.
* In any email templates that you place the {{{[[$account.logo]]}}} tag in.
We may add to this list in future.
!Fonts and colours
The most straightforward branding option is to use the colour pickers and font stack boxes on the branding page. These are integrated into a simple style sheet on each branded page or email. Either use the colour pickers to select foreground or background colours, or paste in your own HTML colour values (hex strings like {{{#ff5533}}}).

[IMG[images/fonts.gif]]

So that you don't end up with something unreadable like white on white, the colours you pick must meet the 4.5:1 minimum relative contrast ratio as given in the [[WCAG 2.0 web accessibility standards|http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html]]; If they don't we'll just ignore them.

As well as being available in your public web pages and standard emails, the fonts and colours are also used in our notification emails.

Fonts are specified as a CSS font stack, which is a list of font names from most to least specific. There are many popular fonts to choose from, but we don't support loading custom web fonts,so you should limit you choices to standard web fonts. A typical font stack might be:
{{{
"Helvetica Neue", Helvetica, Arial, sans-serif
}}}
In this example, Helvetica Neue is typically available on iOS, Helvetica on Mac and some Windows systems (in that order), Arial is available on almost all Windows systems and some Mac, and sans-serif is a generic sans-serif font, which may in fact resolve to Helvetica or Arial anyway. Web browsers will pick the first one in the list that is available to them, so it's imporant to order them from least to most common. There are many other possible font stacks; look at sites like [[this one|http://cssfontstack.com/]] to help you pick one to match your branding.

You can set two font stacks - a heading stack for all levels of HTML headings ({{{h1}}}, {{{h2}}} etc), and a body stack that's used for everything else.

These font stacks and colours are also available as tags to use in your templates.

!Page headers, footers and styles
The boxes allow you to supply HTML snippets for a header and footer that will be placed before and after the main page content, and a CSS style sheet that will allow you to override [[our standard style sheet|https://www.smartmessages.net/css/smartmessages.css]], so you can change fonts, set page width, set backgrounds etc. Here are simple examples of each:
!!Header
{{{
<div id="header" style="background-color: purple; width: 100%; height:100px;text-align: center;"><img alt="Smartmessages header logo" height="63" src="https://www.smartmessages.net/content/smartmessages/images/eheader.gif" style="border: 0px;" width="600"></div>
}}}
!!Footer
{{{
<div id="footer" style="background-color: green; width: 100%; height:100px;text-align: center;"><img alt="email header" height="63" src="https://www.smartmessages.net/content/smartmessages/images/eheader.gif" style="border: 0px;" width="600"></div>
}}}
!!Style Sheet
{{{
.container-fluid {
    width: 960px;
    margin: 0 auto;
}
}}}
While this example uses a fixed width, we recommend that you design header and footer with full-width layouts in mind; that way your styles will work correctly with our responsive layouts when viewed on mobile platforms, where fixed widths generally don't work well. Also be sure to use the {{{header}}} and {{{footer}}} id attributes so that styles can target headers and footers correctly.

The style sheet you set here is loaded ''before'' the styles set when using the colour and font settings above, so if you want to set your fonts and colours in your style sheet, make sure you clear all the font and colour settings.

If you refer to images that are not served over HTTPS (SSL) your users will receive security notices in Internet Explorer, so either upload your graphics through our files area and serve them securely from our ~URLs, or host them yourself on a secure server.
!Notification messages
We are often asked about branding of notifications that are sent when users subscribe and unsubscribe. These messages serve a specific technical role, and they absolutely have to remain working and contain some speciic text in order for us to conform to the necessary legal requirements - allowing them to be edited is extremely likely to compromise that role and thus make both you and us liable for the consequences, so we don't allow it in general. That said, we do make use of a logo as described above, and also the colour and font options. If you set these options, our own branding is limited to a mention in the legal notices and a small footer - your own account name gets top-billing in the message alongside your logo in your own colours and fonts, so there's little chance that your recipients will be confused by it.
While we try to support as many different web browsers as possible, some make it very difficult, in particular Internet Explorer 6, 7 and 8. If you an old version of IE, you may find our site slow or not particularly pretty in some places.

Some browsers are over 80 times faster than ~IE6, so we highly recommend that you upgrade to a browser less than 13 years old, such as a current [[Internet Explorer|http://windows.microsoft.com/en-gb/internet-explorer/download-ie]], [[Firefox|http://www.mozilla.org/en-US/firefox/new/]], [[Chrome|https://www.google.com/chrome/browser/]], [[Opera|http://www.opera.com/]] or [[Safari|http://www.apple.com/safari/]].
As a simpler alternative to using our API, you may be well-served by our callback system. Instead of writing scripts to interrogate our system, we can tell you when something happens in your account, without you having to keep asking (polling). Callbacks are also known as web hooks, and you can [[read more about them here|http://wiki.webhooks.org/]].
On your account settings page, you can enable callbacks, provide us with the URL of a script on your site that should receive these callbacks, and select the events that you'd like to receive callbacks for. It's an efficient mechanism because there is only traffic generated when something happens, but at very busy times you may have a lot of requests coming your way!

[IMG[images/callback.gif]]

Callbacks are ideal for posting events into CRM systems. For example when someone clicks on a link, it could put them into a follow-up contact queue for sales staff in your CRM.
!URL pattern
The callback URL should look like this:
{{{
http://www.example.com/callback.php
}}}
We also support https, username and password, e.g.
{{{
https://user:pass@www.example.com/callback.php
}}}
If you don't use password protection, you can improve the security of your script by giving it a less guessable name, for example:
{{{
http://www.example.com/callback924a8ceeac17f54d3be3f8cdf1c04eb2.php
}}}
The only real restrictions are that your URL should be valid and should not contain a query string (starting with ?) or anchor location/fragment part (starting with #), and not be in a private or inaccesssible network (e.g. {{{192.168.*}}} or {{{localhost}}}). Of course you don't have to implement your handler in PHP - you can use any language you like - and it's generally very easy to implement handlers for the straightforward requests we use.
!Security
It's in your interests to validate that the request you get looks like the documentation below describes before acting upon it so as to avoid security issues with malicious users attempting to inject bad data via your callback. For example, force mailshot ~IDs to be integers, ignore any text in those fields. Our sample script applies some simple validation. You can also firewall by our source ~IPs (though be aware that they are subject to change) which will be in the networks {{{93.93.128.0/24}}} and {{{93.93.135.0/24}}}.
!Events
These are the callback events that we can send you:
* subscribe
* unsubscribe
* open
* click
* bounce
* spamreport
* mailshotstarted
* mailshotcomplete
* listuploadcomplete
* listuploadfailed
Below you'll find descriptions of parameter names and values that will be supplied with each of these requests.
When you receive a request from us (which will be in the form of a normal [[HTTP POST|http://en.wikipedia.org/wiki/HTTP_POST]]), all we want in return is a normal {{{HTTP/1.0 200 OK}}} success response (which is the default in languages like PHP, so you don't have to do anything special). We will also accept {{{201 Created}}} or {{{202 Accepted}}} responses. We give you 15 seconds to handle the request, but if your callback URL fails to respond, returns an error or a success code we're not expecting, we will retry the callback after 30 seconds, 10 minutes, 1 hour, and 1 day, after which we'll disable your callback and email all your account admins saying we've done so. We don't log bad responses and we ignore response bodies. ''It's up to you to make sure your system is available and working!'' When you've fixed your callback you can re-enable it on your account settings page. While callbacks are disabled, we don't post any new events.
You can get and set your callback URL on your account settings page or via our {{{getcallbackurl}}} / {{{setcallbackurl}}} API functions.
!!Performance considerations
None of the CRM systems we've encountered are anywhere near fast enough to handle events at the full rate at which we can send events to you (hundreds per second); for example we found that salesforce.com takes up to two seconds to handle a single event. Some of our bigger customers receive peaks of 150 message openings per second alone, so please bear in mind these performance constraints when you are writing your script and selecting the events you want to receive as they will be coming thick and fast. A good way of dealing with 'peaky' traffic is to place the incoming callback requests into a queueing system (for example [[ZeroMQ|http://www.zeromq.org/]], [[beanstalkd|http://kr.github.com/beanstalkd/]] or [[Amazon's SQS|http://aws.amazon.com/sqs/]]) so you can process them at leisure rather than trying to handle them immediately; We use beanstalkd for our outbound queue.
We don't provide a 'send' callback event, but you can get a list of people that are on a mailing list used for a particular mailshot through our web interface or via our API; a more efficient batch operation instead of receiving send events.
!!Open
*//event//: The word 'open'.
*//address//: The email address that opened a message.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//mailshotid//: The Smartmessages ID of the mailshot the message was in (compatible with API functions).
*//mailshotname//: The name of the mailshot the message was in (as shown on your mailings page).
*//ip//: The IP address of the user that opened the message.
*//agent//: The HTTP user-agent string of the user that opened the message.
!!Click
*//event//: The word 'click'.
*//address//: The email address that clicked.
*//url//: The url they clicked.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//mailshotid//: The Smartmessages ID of the mailshot the link was in (compatible with API functions).
*//mailshotname//: The name of the mailshot the link was in (as shown on your mailings page).
*//ip//: The IP address of the user that clicked the link.
*//agent//: The HTTP user-agent string of the user that clicked the link.
!!Subscribe
*//event//: The word 'subscribe'.
*//address//: The email address that subscribed.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//listid//: The Smartmessages ID of the list they subscribed to (compatible with API functions).
*//listname//: The name of the mailing list they subscribed to (as shown on your contacts page).
*//ip//: The IP address of the user that requested the subscribe.
*//agent//: The HTTP user-agent string of the user that requested the subscribe.
You won't receive subscribe events until subscriptions are confirmed via double-opt-in.
!!Unsubscribe
*//event//: The word 'unsubscribe'.
*//address//: The email address that unsubscribed.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//listid//: The Smartmessages ID of the list they unsubscribed from (compatible with API functions).
*//listname//: The name of the mailing list they unsubscribed from (as shown on your contacts page).
*//ip//: The IP address of the user that requested the unsubscribe.
*//agent//: The HTTP user-agent string of the user that requested the unsubscribe.
!!Spam Report
*//event//: The word 'spamreport'.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//address//: The email address that reported a message as spam.
*//mailshotid//: The Smartmessages ID of the mailshot (compatible with API functions).
*//mailshotname//: The name of the mailshot (as shown on your mailings page).
We only receive these from a few major ~ISPs, however it's vitally important to the health of your lists that you remove them at source as well as in Smartmessages (which happens automatically anyway).
!!Bounce
*//event//: The word 'bounce'.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//address//: The email address that bounced.
*//reasoncode//: Our internal bounce reason code (See [[Bounces]]).
*//severity//: The bounce severity (hard, soft, other).
*//reason//: A text description of the bounce reason.
We only emit bounce callbacks when an address has received sufficient bounces to warrant removing it from lists. The point at which this happens is quite involved. For some ~ISPs (e.g. Yahoo!), a single hard bounce is considered fatal, for others an address may not be considered dead until several have been received. For this reason we don't emit callbacks for every bounce, only ones that result in an address being removed from lists, so in general you won't receive callbacks for soft bounces. In some cases the specific bounce reason stated may not sound fatal (e.g. mailbox full), but it may be the last in a long sequence of similar failures that has led to address removal.
!!Mailshot Started
*//event//: The word 'mailshotstarted'.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//mailshotid//: The Smartmessages ID of the mailshot (compatible with API functions).
*//mailshotname//: The name of the mailshot (as shown on your mailings page).
!!Mailshot Complete
*//event//: The word 'mailshotcomplete'.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//mailshotid//: The Smartmessages ID of the mailshot (compatible with API functions).
*//mailshotname//: The name of the mailshot (as shown on your mailings page).
!!List Upload Complete
*//event//: The word 'listuploadcomplete'.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//listid//: The Smartmessages ID of the mailing list (compatible with API functions).
*//uploadid//: The Smartmessages ID of the upload (compatible with API functions).
*//listname//: The name of the mailing list (as shown on your contacts page).
!!List Upload Failed
*//event//: The word 'listuploadfailed'.
*//time//: The time this happened (~ISO8601 '{{{YYYY-MM-DD HH:MM:SS}}}' format, UTC time zone).
*//listid//: The Smartmessages ID of the mailing list (compatible with API functions).
*//uploadid//: The Smartmessages ID of the upload (compatible with API functions).
*//listname//: The name of the mailing list (as shown on your contacts page).
*//message//: A text message explaining why the upload failed, e.g. misnamed fields, corrupt file etc.
!Example code
Here is a short PHP script to accept a callback request, apply some validation and log the incoming request as a simple tab-delimited text file:
{{{
<?php
if (array_key_exists('ping', $_REQUEST)) {
	echo "pong\n";
	exit;
}
if (!array_key_exists('event', $_POST)) {
	exit;
}
$record = '';
try {
	switch($_POST['event']) {
		case 'send':
			if (array_keys_exist(array('address', 'time', 'listid', 'listname', 'mailshotid', 'mailshotname'), $_POST)) {
				$record .= "send\t".
							substr(strip_tags($_POST['address']), 0, 100)."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19)."\t".
							(integer)$_POST['listid']."\t".
							substr(strip_tags($_POST['listname']), 0, 100);
							(integer)$_POST['mailshotid']."\t".
							substr(strip_tags($_POST['mailshotname']), 0, 100);
			} else {
				throw new Exception('Invalid '.$_POST['event'].': '.serialize($_POST));
			}
			break;
		case 'subscribe':
			if (array_keys_exist(array('address', 'address', 'time', 'listid', 'listname', 'ip', 'agent'), $_POST)) {
				$record .= $_POST['event']."\t".
							substr(strip_tags($_POST['address']), 0, 100)."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19)."\t".
							(integer)$_POST['listid']."\t".
							substr(strip_tags($_POST['listname']), 0, 100)."\t".
							substr(strip_tags($_POST['ip']), 0, 15)."\t".
							substr(strip_tags($_POST['agent']), 0, 255);
			}
			break;
		case 'unsubscribe':
			if (array_keys_exist(array('address', 'address', 'time', 'listid', 'listname', 'ip', 'agent'), $_POST)) {
				$record .= $_POST['event']."\t".
							substr(strip_tags($_POST['address']), 0, 100)."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19)."\t".
							(integer)$_POST['listid']."\t".
							substr(strip_tags($_POST['listname']), 0, 100)."\t".
							substr(strip_tags($_POST['ip']), 0, 15)."\t".
							substr(strip_tags($_POST['agent']), 0, 255);
			}
			break;
		case 'click':
			if (array_keys_exist(array('address', 'url', 'time', 'mailshotid', 'mailshotname', 'ip', 'agent'), $_POST)) {
				$record .= "click\t".
							substr(strip_tags($_POST['address']), 0, 100)."\t".
							substr(strip_tags($_POST['url']), 0, 255)."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19)."\t".
							(integer)$_POST['mailshotid']."\t".
							substr(strip_tags($_POST['mailshotname']), 0, 100)."\t".
							substr(strip_tags($_POST['ip']), 0, 15)."\t".
							substr(strip_tags($_POST['agent']), 0, 255);
			} else {
				throw new Exception('Invalid '.$_POST['event'].': '.serialize($_POST));
			}
			break;
		case 'open':
			if (array_keys_exist(array('address', 'time', 'mailshotid', 'mailshotname'), $_POST)) {
				$record .= "open\t".
							substr(strip_tags($_POST['address']), 0, 100)."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19)."\t".
							(integer)$_POST['mailshotid']."\t".
							substr(strip_tags($_POST['mailshotname']), 0, 100)."\t".
							substr(strip_tags($_POST['ip']), 0, 15)."\t".
							substr(strip_tags($_POST['agent']), 0, 255);
			} else {
				throw new Exception('Invalid '.$_POST['event'].': '.serialize($_POST));
			}
			break;
		case 'bounce':
			if (array_keys_exist(array('address', 'time', 'severity', 'reasoncode', 'reason'), $_POST)) {
				$record .= "bounce\t".
							substr(strip_tags($_POST['address']), 0, 100)."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19)."\t".
							substr(strip_tags($_POST['severity']), 0, 100)."\t".
							(integer)$_POST['reasoncode']."\t".
							substr(strip_tags($_POST['reason']), 0, 100);
			} else {
				throw new Exception('Invalid '.$_POST['event'].': '.serialize($_POST));
			}
			break;
		case 'spamreport':
			if (array_keys_exist(array('address', 'time'), $_POST)) {
				$record .= "spamreport\t".
							substr(strip_tags($_POST['address']), 0, 100)."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19);
			} else {
				throw new Exception('Invalid '.$_POST['event'].': '.serialize($_POST));
			}
			break;
		case 'mailshotstarted':
		case 'mailshotcomplete':
			if (array_keys_exist(array('time', 'mailshotid', 'mailshotname'), $_POST)) {
				$record .= $_POST['event']."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19)."\t".
							(integer)$_POST['mailshotid']."\t".
							substr(strip_tags($_POST['mailshotname']), 0, 100);
			} else {
				throw new Exception('Invalid '.$_POST['event'].': '.serialize($_POST));
			}
			break;
		case 'listuploadfailed':
			if (array_keys_exist(array('time', 'listid', 'listname', 'uploadid', 'message'), $_POST)) {
				$record .= $_POST['event']."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19)."\t".
							(integer)$_POST['listid']."\t".
							substr(strip_tags($_POST['listname']), 0, 100)."\t".
							(integer)$_POST['uploadid']."\t".
							substr(strip_tags($_POST['message']), 0, 100);
			} else {
				throw new Exception('Invalid '.$_POST['event'].': '.serialize($_POST));
			}
			break;
		case 'listuploadcomplete':
			if (array_keys_exist(array('time', 'listid', 'listname', 'uploadid'), $_POST)) {
				$record .= $_POST['event']."\t".
							substr(preg_replace('/[^ 0-9\:-]/', '', $_POST['time']), 0, 19)."\t".
							(integer)$_POST['listid']."\t".
							substr(strip_tags($_POST['listname']), 0, 100)."\t".
							(integer)$_POST['uploadid'];
			} else {
				throw new Exception('Invalid '.$_POST['event'].': '.serialize($_POST));
			}
			break;
		default:
			//If we get here we've received an unexpected callback type
			throw new Exception('Unknown callback type: '.serialize($_POST));
			break;
	}
} catch (Exception $e) {
	$record = 'Invalid callback request received: '.$e->getMessage();
}

if (!empty($record)) {
	file_put_contents('callback.txt', $record."\n", FILE_APPEND | LOCK_EX);
}

function array_keys_exist($keys, $array) {
	if (!is_array($keys)) {
		return array_key_exists($keys, $array);
	} else {
		foreach($keys as $key) {
			if (!array_key_exists($key, $array)) {
				return false;
			}
		}
	}
	return true; //didn't fail to find one, so must all be present
}
}}}
Background: #faf3e4
Foreground: #5d574a
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #0088cc
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #554E3E
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
Please contact us if you have any suggestions, complaints or general comments. 

[[support@smartmessages.net|mailto:support@smartmessages.net?subject=Support Request]]

If you're a recipient who wants to be removed from a list, use the unsubscribe link in the message you received. They are there for a reason and will work far faster and more efficiently than anything we can do manually.
Continuous mailshots are a kind of auto-responder. You create a continuous mailshot just list any other mailshot, but under the 'To' tab, select a mailing list, and then check the 'Continuous mailshot' checkbox. When you send a continuous mailshot, ''no messages are sent to the existing list''. When someone subscribes to the list, they ''will'' be sent the message as configured within the mailshot.

[IMG[images/continuous.gif]]

This makes them ideal for sending a 'welcome' message to anyone that signs up for one of your mailing lists. You might normally send a monthly newsletter to your 'newsletter' mailing list, but you can set up a welcome message (using all the normal template features) to thank them for signing up. As their name suggests, continuous mailshots have no particular send time - new subscribers will be sent a message a few seconds after they subscribe. After they have been activated, you can pause/continue/stop continuous mailshots individually on the mailings page.

When you have a double-opt-in subscription policy, messages will only be sent after a double opt-in veerification loop has been completed.

List uploads are exempted from continuous mailshots, so after uploading a list into a list that's being used by an active continuous mailshot, the new subscribers will ''not'' be sent messages.
We offer a range of customised projects, using Smartmessages at the centre, and then coding in custom forms, with a complete business process from start to finish.
[[DomainKeys|http://en.wikipedia.org/wiki/Domainkeys]] and its newer replacement [[DKIM|http://en.wikipedia.org/wiki/DomainKeys_Identified_Mail]] (~DomainKeys Identified Mail) are anti-forgery technologies that help prevent phishing and spam, and as such increase the trust in messages that use them. ~DomainKeys is now deprecated and should not be used for new deployments.
DKIM is a step beyond what [[SPF and SenderID|SPF]] can offer for forgery prevention, at the expense of some complexity. While SPF attempts to ensure that the //origin// of the message is not forged, DKIM ensures that the //content// of a message is not forged or altered, thus the two technologies complement each other nicely; DKIM is not in any way a replacement for SPF. For ultimate trust in your email, you should use both.
DKIM is a replacement for the older and less-capable ~DomainKeys, and we only implement DKIM in Smartmessages. Like SPF, it requires that you create records in your DNS server - how you achieve that will vary widely, but if you own your own domain, you should be able to access that through your registrar or DNS hosting service (incidentally, we highly recommend [[Gandi.net|http://www.gandi.net/]] for domain registration and DNS hosting).
!Why?
What follows may seem like a lot of effort, and all too complicated, so why do it? Well, since you're here, you're obviously interested in getting email in front of your audience, in their inbox, not their spam folder. DKIM can help this - although there is nothing preventing spammers from using DKIM (and some do), it means that the receiver knows that what you sent is what you meant to send, and that you are not lying about who you are (courtesy of SPF). That helps them to trust the messages they receive from you, which can, when coupled with a consistent reputation of not sending messages that are reported as spam, result in more reliable inbox placement. Some email hosts display a little icon to the user indicating that a message is from a trusted source, further enhancing trust in your subscribers.
There's another key reason: Yahoo!, one of the inventors and biggest supporters of DKIM, requires that we use DKIM if we want to receive [[spam reports|SpamReports]] about messages we send for you. ''This is the single biggest driver for improving your sending reputation, deliverability and list quality!'' Mail hosts that are known to pay significant attention to DKIM signed email include Yahoo! and Google, and more are adding it every day. It's rapidly becoming the case that ''if you don't use DKIM, you can pretty much forget about being able to deliver volume to any Yahoo! domains'', which includes some other large UK ~ISPs such as btinternet.com and talk21.com. Convinced? Read on...
!!What's the 'via' thing I see in gmail?
We sign all messages with DKIM: if you have our key configured in your DNS, we use that, otherwise we sign using our sending domain, {{{mail.smartmessages.net}}}. In the latter case, gmail will display your message like this:
{{{
From: Joe User joe@example.com via mail.smartmessages.net
}}}
If you set up our DKIM key, it will not display the {{{via...}}} part.
!!Which domain?
When you send messages through Smartmessages, you have the opportunity to enter a 'from' address, such as 'newsletter@example.com'. The DKIM records need to match the domain part (the bit after the '@', in this case 'example.com') of your from address. If you use several domains, you can create the same DNS records in each of them, and register them all in Smartmessages, and we will use the right one automatically. Note that you need to have control of the DNS for the domain in question.
!!What do I need to do?
If you don't already have DKIM in your DNS, you will need to create a DNS record with our DKIM selector ({{{sm2}}} for Smartmessages) that contains our DKIM key. Selectors allow different mail sources to sign messages independently, for example by different branch offices, or (gasp!) other mailing list management providers, each using their own selector.

Exactly how you change your DNS records will vary depending on what provider you use - you'll need to consult their help.

Create another TXT record and name it {{{sm2._domainkey}}}. The value of the record should be set to this:
{{{
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqKmFo0mnBMoJeJYbVvwSOflmISd5jkKAO0xowlJUEE+PYHXt7hgHFqc7SiFj2sKN2R8xI4ifFgz/0zuVYUe9gGT4cpuVy1Gu746K+R5tSd7EmnXgZe2PFogu/b+YABjQaR9GpF7VuZCXXGyueCL75Pabx3QzRr8ZtIFIUfqNyhH2WlaodFQqfxiPbxmWMlNhs8/mXDtijlt7b56hrWHTq+Ash/ut4OB53cZF8PBxNt8eCJXrdV0aqqefFJzmFDVSFTykWZ63tL7VKYvrVEvKS2BwjRI0pqrfpnLFeQbNV6waUEPAFZOHa/FQr01jSh5BJtHPQHp8COBzYoTqjeBKaQIDAQAB
}}}
If your DNS software complains about this value, try prefixing the semi-colons with a backslash, like {{{v=DKIM1\; k=rsa\;}}}, leaving the rest unchanged. 
!!Alternative selectors
The DKIM key for the {{{sm2}}} selector is 2048 bits in length, and is what we (and google) recommend. Some DNS systems may have trouble with large TXT records like this, but there are some workarounds for that. Firstly, many DNS servers may allow you to have multiple strings for a single record, in which case you should be able to split into two parts (usually in double quotes) like this:
{{{
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqKmFo0mnBMoJeJYbVvwSOflmISd5jkKAO0xowlJUEE+PYHXt7hgHFqc7SiFj2sKN2R8xI4ifFgz/0zuVYUe9gGT4cpuVy1Gu746K+R5tSd7EmnXgZe2PFogu/b+YABjQaR9GpF7VuZCXXGyueCL75Pabx3QzRr8ZtIFIUfqNyhH2Wl" "aodFQqfxiPbxmWMlNhs8/mXDtijlt7b56hrWHTq+Ash/ut4OB53cZF8PBxNt8eCJXrdV0aqqefFJzmFDVSFTykWZ63tL7VKYvrVEvKS2BwjRI0pqrfpnLFeQbNV6waUEPAFZOHa/FQr01jSh5BJtHPQHp8COBzYoTqjeBKaQIDAQAB"
}}}
Alternatively you can use the {{{sm1}}} selector and this 1024-bit key:
{{{
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5pjTQQoPqW+SwCw+QAbmI+uPuk1hPxYTummB4dtzw4QQLZZR8miTnHnmknKz+LMGMYioAHF2BvHjPsI4ixlFKo0ByyY32xfpF8V5TgdYciv8Bfrm1Iiinmlm5SvLOqP81TQ5G4hat7ccCsHP9gDJR/9J0ZV9gHHkzwBCLUK+njwIDAQAB
}}}
or for really limited systems, our original 384-bit key with the {{{sm}}} selector:
{{{
v=DKIM1; k=rsa; p=MEwwDQYJKoZIhvcNAQEBBQADOwAwOAIxALRHPRnFUks4Hntg7AidEbw3zZGWhVXAy4Gm6TiP1Ln96IwGV1Knde4bJClKTvsGTQIDAQAB
}}}
Note that Google (and possibly others) ignores DKIM signatures that have less than 1024 bits so we strongly recommend you upgrade to a longer key if you are using this old one.
If you set up multiple selectors we will automatically use the longest one, but you ''must verify it on the settings page''.
!!How do I know it's working?
Once you've set it up, you can check it (and everything else in your DNS) is working using a service like dnsreport. If you have a Unix/Linux/~MacOS X command line handy, you can try {{{dig txt _domainkey.example.com}}} and {{{dig txt sm2._domainkey.example.com}}} to check that your DNS is publishing your records correctly. Alternatively use [[this tool|http://domainkeys.sourceforge.net/policycheck.html]] for testing your policy, [[this one |http://dkimcore.org/tools/dkimrecordcheck.html]] for your selectors.
Once you've set up your DNS, you need to enter the domains you send from on the settings page of your Smartmessages account. When you do so, it will check SPF and DKIM entries and confirm that they are OK and what key length they are using. When you send correctly ~DKIM-signed messages to Yahoo! addresses recipients will see a small key-shaped icon next to your from address, indicating that it's validated.
!!!Here's one I made earlier
A complete set of DKIM and SPF DNS records for working with Smartmessages might look like this (exactly how it is presented will depend on your provider). This example contains both the {{{sm2}}} 2048-bit key and the legacy {{{sm}}} 384-bit key:
{{{
example.com.            10800   IN      TXT     "v=spf1 a mx include:smartmessages.net ~all"
_domainkey.example.com. 10800   IN      TXT     "o=~"
sm2._domainkey.example.com. 10800 IN     TXT     "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqKmFo0mnBMoJeJYbVvwSOflmISd5jkKAO0xowlJUEE+PYHXt7hgHFqc7SiFj2sKN2R8xI4ifFgz/0zuVYUe9gGT4cpuVy1Gu746K+R5tSd7EmnXgZe2PFogu/b+YABjQaR9GpF7VuZCXXGyueCL75Pabx3QzRr8ZtIFIUfqNyhH2Wl" "aodFQqfxiPbxmWMlNhs8/mXDtijlt7b56hrWHTq+Ash/ut4OB53cZF8PBxNt8eCJXrdV0aqqefFJzmFDVSFTykWZ63tL7VKYvrVEvKS2BwjRI0pqrfpnLFeQbNV6waUEPAFZOHa/FQr01jSh5BJtHPQHp8COBzYoTqjeBKaQIDAQAB"
sm._domainkey.example.com. 10800 IN     TXT     "v=DKIM1; k=rsa; p=MEwwDQYJKoZIhvcNAQEBBQADOwAwOAIxALRHPRnFUks4Hntg7AidEbw3zZGWhVXAy4Gm6TiP1Ln96IwGV1Knde4bJClKTvsGTQIDAQAB"
}}}
!!How do I register domains with Smartmessages?
Once you've set up your DNS, log into your Smartmessages account and go to the 'settings' tab in the 'account' page. At the bottom you will see an 'authenticated domains' section. Add your domain there and it will automatically check that your DNS contains the correct values. If you get some little red crosses instead of green ticks, double-check your DNS entries according to this guide, and you can use the 'recheck' button to try again. If you still have trouble, [[contact us|http://www.synchromedia.co.uk/about-us/contact-us/]]. Note that DNS changes can take a while to refresh (though additions are instant), so it may not give you the all-clear for several hours if you don't get it right first time.
!!How does it work?
DKIM uses [[public-key cryptography|http://en.wikipedia.org/wiki/Public_key_cryptography]], a technique which uses mathematically-related public and private keys to encrypt and decrypt data. Our public key (All that random-looking text in the DNS record) is placed in //your// DNS so that when an email is received (which contains an encrypted signature created using our //private// key), the email server can do a DNS lookup using our {{{sm2}}} selector and use the public key it finds there to verify that the email is legitimate and hasn鴠been tampered with in transit. The public key needs to be in //your// DNS in order to prove that //we// are not just making it up!
!!What if I don't set up DKIM?
You don't have to use DKIM; if you don't, we will still sign the message as an intermediary, which doesn't carry nearly as much weight as your own signature. It's rapidly becoming the case that ''if you don't use DKIM, you can pretty much forget about being able to deliver volume to any Yahoo! domains'', which includes some other large UK ~ISPs such as btinternet.com and talk21.com.
[[DMARC|http://www.dmarc.org/]] stands for "Domain-based Message Authentication, Reporting & Conformance", is a technical specification created by a group of organizations that want to help reduce the potential for email-based abuse by solving a couple of long-standing operational, deployment, and reporting issues related to email authentication protocols.

When you make use of email authentication protocols, specifically [[SPF]] and [[DKIM|DKIM and DomainKeys]], they describe how to identify whether a message has come from a valid source and whether it has been tampered with in transit. They don't say what to do when you encounter a message that fails these tests. Should you bounce it to the sender? Report it to the police? The problem is you don't really know what to do, and DMARC is here to address that. DMARC can be extremely effective; dmarc.org reports that twitter saw phishing attacks against them drop from 110 //million// per day to under 1000!

Generally setting up DMARC is quite similar to setting up DKIM, so you'll need to be familiar with creating new records in your DNS - we can help you with that. Most users simply set up a passive record that merely confirms that messages failing tests should be rejected, but it's interesting (especially if you have a high-value service that's prone to [[phishing|Phishing]] attacks) to get feedback on whether these attacks are occuring. You can set up your own system for that, but it's much easier to use a service for it - we recommend [[|Dmarcian.com|https://dmarcian.com/]].
[[Start here]]
This happens under the contacts tabs. All files can be opened by Excel (CVS).

[IMG[images/contact.gif]]

1. Click the ''manage'' button:  [IMG[images/manage.gif]]

2. Click the ''actions'' tab: [IMG[images/list.gif]]

3. Cick the ''download list'' button:

[IMG[images/download.gif]]
Email addresses are surprisingly complex and tricky things, and there are many addresses that you might not think are valid that are, and vice versa. For example, this is a valid email address: {{{!#$%&'*+-/=?^_`{}|~@example.org}}}, but this is not: {{{joe_user@example_site.org}}}. Many web sites don't understand this and reject some perfectly valid addresses, much to the annoyance of their users. This is especially common for the '+' character which is used when creating extremely useful 'disposable' addresses such as {{{user+something@example.com}}}, something that is supported on Gmail, Yahoo! and Hotmail/Live. You can name and shame sites that don't know what email addresses look like [[here|http://youdontknowemail.tumblr.com]].

That said, many strictly valid email addresses are unlikely to be correct, so it's common to only allow a subset, but any validation should //never// allow something that is really invalid, no matter what subset is chosen (for example it's always invalid for a domain to contain an underscore).

It's important to note that an address that's valid doesn't necessarily exist (i.e. it may still bounce if you email it), though an invalid address is guaranteed not to exist, so it's not worth sending to in the first place.

The applicable standards for email addresses are the original [[RFC822|http://tools.ietf.org/html/rfc822]] and its updates from ~RFCs [[2822|http://tools.ietf.org/html/rfc2822]] and [[5322|http://tools.ietf.org/html/rfc5322]], and related definitions in [[RFC821|http://tools.ietf.org/html/rfc821]], [[2821|http://tools.ietf.org/html/rfc2821]], [[5321|http://tools.ietf.org/html/rfc5321]] and [[1035|http://tools.ietf.org/html/rfc1035]]. ~RFC822 on its own isn't very useful for validation because it's missing updates and contains conflicts with these other standards.

!!Validation in Smartmessages

We avoid problems with invalid addresses by simply not allowing them into our system. They will be rejected in subscription requests, ignored (but reported to you) in list uploads etc. Smartmessages uses the definition of an email address from the [[official HTML5 specification of an email address|http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)]], which we wrote.

!!Validating in your own code

You can use the {{{validateaddress}}} function in [[our API|API]] to check against the same validation we use internally, but we don't recommend that for high volumes as it's much more efficient to check addresses locally.

The most common way to validate in your own code is to use regular expressions, which is supported in almost all programming languages in some form. You'll find huge numbers of regular expressions for validating email if you search for them, but many are wrong, or at least wrong enough to be likely to annoy valid address holders or to allow invalid addresses. There are several validation expressions we recommend:
* [[Ex-parrot's RFC822 validator|http://www.ex-parrot.com/pdw/Mail-RFC822-Address.html]]. This is a complete ~RFC822 expression in perl, and shows just how horribly complicated a correct expression can be.
* [[Michael Rushton's PHP regex|http://squiloople.com/2009/12/20/email-address-validation/]]. This is also the expression used in [[PHPMailer|https://github.com/PHPMailer/PHPMailer]].
* [[PHP's built-in filter_var function|http://www.php.net/manual/en/function.filter-var.php]] with the {{{FILTER_VALIDATE_EMAIL}}} flag set. This is built-in to PHP 5.2 and later. It uses Michael Rushton's expression, with one change to not allow 'dotless' domains like 'a@b', which are not permitted by ICANN even though they are valid in ~RFC822.
* [[HTML5's email definition|http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)]], which we wrote, attempts to provide a sensible compromise between allowing valid addresses and rejecting likely bad ones. This is what we are currently using within Smartmessages.
/***
|Name|ExportTiddlersPlugin|
|Source|http://www.TiddlyTools.com/#ExportTiddlersPlugin|
|Documentation|http://www.TiddlyTools.com/#ExportTiddlersPluginInfo|
|Version|2.9.6|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|interactively select/export tiddlers to a separate file|
!!!!!Documentation
>see [[ExportTiddlersPluginInfo]]
!!!!!Inline control panel (live):
><<exportTiddlers inline>>
!!!!!Revisions
<<<
2011.02.14 2.9.6 fix OSX error: use picker.file.path
2010.02.25 2.9.5 added merge checkbox option and improved 'merge' status message
|please see [[ExportTiddlersPluginInfo]] for additional revision details|
2005.10.09 0.0.0 development started
<<<
!!!!!Code
***/
//{{{
// version
version.extensions.ExportTiddlersPlugin= {major: 2, minor: 9, revision: 6, date: new Date(2011,2,14)};

// default shadow definition
config.shadowTiddlers.ExportTiddlers='<<exportTiddlers inline>>';

// add 'export' backstage task (following built-in import task)
if (config.tasks) { // TW2.2 or above
	config.tasks.exportTask = {
		text:'export',
		tooltip:'Export selected tiddlers to another file',
		content:'<<exportTiddlers inline>>'
	}
	config.backstageTasks.splice(config.backstageTasks.indexOf('importTask')+1,0,'exportTask');
}

config.macros.exportTiddlers = {
	$: function(id) { return document.getElementById(id); }, // abbreviation
	label: 'export tiddlers',
	prompt: 'Copy selected tiddlers to an export document',
	okmsg: '%0 tiddler%1 written to %2',
	failmsg: 'An error occurred while creating %1',
	overwriteprompt: '%0\ncontains %1 tiddler%2 that will be discarded or replaced',
	mergestatus: '%0 tiddler%1 added, %2 tiddler%3 updated, %4 tiddler%5 unchanged',
	statusmsg: '%0 tiddler%1 - %2 selected for export',
	newdefault: 'export.html',
	datetimefmt: '0MM/0DD/YYYY 0hh:0mm:0ss',  // for 'filter date/time' edit fields
	type_TW: "tw", type_PS: "ps", type_TX: "tx", type_CS: "cs", type_NF: "nf", // file type tokens
	type_map: { // maps type param to token values
		tiddlywiki:"tw", tw:"tw", wiki: "tw",
		purestore: "ps", ps:"ps", store:"ps",
		plaintext: "tx", tx:"tx", text: "tx",
		comma:     "cs", cs:"cs", csv:  "cs",
		newsfeed:  "nf", nf:"nf", xml:  "nf", rss:"nf"
	},
	handler: function(place,macroName,params) {
		if (params[0]!='inline')
			{ createTiddlyButton(place,this.label,this.prompt,this.togglePanel); return; }
		var panel=this.createPanel(place);
		panel.style.position='static';
		panel.style.display='block';
	},
	createPanel: function(place) {
		var panel=this.$('exportPanel');
		if (panel) { panel.parentNode.removeChild(panel); }
		setStylesheet(store.getTiddlerText('ExportTiddlersPlugin##css',''),'exportTiddlers');
		panel=createTiddlyElement(place,'span','exportPanel',null,null)
		panel.innerHTML=store.getTiddlerText('ExportTiddlersPlugin##html','');
		this.initFilter();
		this.refreshList(0);
		var fn=this.$('exportFilename');
		if (window.location.protocol=='file:' && !fn.value.length) {
			// get new target path/filename
			var newPath=getLocalPath(window.location.href);
			var slashpos=newPath.lastIndexOf('/'); if (slashpos==-1) slashpos=newPath.lastIndexOf('\\'); 
			if (slashpos!=-1) newPath=newPath.substr(0,slashpos+1); // trim filename
			fn.value=newPath+this.newdefault;
		}
		return panel;
	},
	togglePanel: function(e) { var e=e||window.event;
		var cme=config.macros.exportTiddlers; // abbrev
		var parent=resolveTarget(e).parentNode;
		var panel=cme.$('exportPanel');
		if (panel==undefined || panel.parentNode!=parent)
			panel=cme.createPanel(parent);
		var isOpen=panel.style.display=='block';
		if(config.options.chkAnimate)
			anim.startAnimating(new Slider(panel,!isOpen,e.shiftKey || e.altKey,'none'));
		else
			panel.style.display=isOpen?'none':'block' ;
		if (panel.style.display!='none') {
			cme.refreshList(0);
			cme.$('exportFilename').focus(); 
			cme.$('exportFilename').select();
		}
		e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); return(false);
	},
	process: function(which) { // process panel control interactions
		var theList=this.$('exportList'); if (!theList) return false;
		var count = 0;
		var total = store.getTiddlers('title').length;
		switch (which.id) {
			case 'exportFilter':
				count=this.filterExportList();
				var panel=this.$('exportFilterPanel');
				if (count==-1) { panel.style.display='block'; break; }
				this.$('exportStart').disabled=(count==0);
				this.$('exportDelete').disabled=(count==0);
				this.displayStatus(count,total);
				if (count==0) { alert('No tiddlers were selected'); panel.style.display='block'; }
				break;
			case 'exportStart':
				this.go();
				break;
			case 'exportDelete':
				this.deleteTiddlers();
				break;
			case 'exportHideFilter':
			case 'exportToggleFilter':
				var panel=this.$('exportFilterPanel')
				panel.style.display=(panel.style.display=='block')?'none':'block';
				break;
			case 'exportSelectChanges':
				var lastmod=new Date(document.lastModified);
				for (var t = 0; t < theList.options.length; t++) {
					if (theList.options[t].value=='') continue;
					var tiddler=store.getTiddler(theList.options[t].value); if (!tiddler) continue;
					theList.options[t].selected=(tiddler.modified>lastmod);
					count += (tiddler.modified>lastmod)?1:0;
				}
				this.$('exportStart').disabled=(count==0);
				this.$('exportDelete').disabled=(count==0);
				this.displayStatus(count,total);
				if (count==0) alert('There are no unsaved changes');
				break;
			case 'exportSelectAll':
				for (var t = 0; t < theList.options.length; t++) {
					if (theList.options[t].value=='') continue;
					theList.options[t].selected=true;
					count += 1;
				}
				this.$('exportStart').disabled=(count==0);
				this.$('exportDelete').disabled=(count==0);
				this.displayStatus(count,count);
				break;
			case 'exportSelectOpened':
				for (var t=0; t<theList.options.length; t++) theList.options[t].selected=false;
				var tiddlerDisplay=this.$('tiddlerDisplay');
				for (var t=0; t<tiddlerDisplay.childNodes.length;t++) {
					var tiddler=tiddlerDisplay.childNodes[t].id.substr(7);
					for (var i=0; i<theList.options.length; i++) {
						if (theList.options[i].value!=tiddler) continue;
						theList.options[i].selected=true; count++; break;
					}
				}
				this.$('exportStart').disabled=(count==0);
				this.$('exportDelete').disabled=(count==0);
				this.displayStatus(count,total);
				if (count==0) alert('There are no tiddlers currently opened');
				break;
			case 'exportSelectRelated':
				// recursively build list of related tiddlers
				function getRelatedTiddlers(tid,tids) {
					var t=store.getTiddler(tid); if (!t || tids.contains(tid)) return tids;
					tids.push(t.title);
					if (!t.linksUpdated) t.changed();
					for (var i=0; i<t.links.length; i++)
						if (t.links[i]!=tid) tids=getRelatedTiddlers(t.links[i],tids);
					return tids;
				}
				// for all currently selected tiddlers, gather up the related tiddlers (including self) and select them as well
				var tids=[];
				for (var i=0; i<theList.options.length; i++)
					if (theList.options[i].selected) tids=getRelatedTiddlers(theList.options[i].value,tids);
				// select related tiddlers (includes original selected tiddlers)
				for (var i=0; i<theList.options.length; i++)
					theList.options[i].selected=tids.contains(theList.options[i].value);
				this.displayStatus(tids.length,total);
				break;
			case 'exportListSmaller':	// decrease current listbox size
				var min=5;
				theList.size-=(theList.size>min)?1:0;
				break;
			case 'exportListLarger':	// increase current listbox size
				var max=(theList.options.length>25)?theList.options.length:25;
				theList.size+=(theList.size<max)?1:0;
				break;
			case 'exportClose':
				this.$('exportPanel').style.display='none';
				break;
		}
		return false;
	},
	displayStatus: function(count,total) {
		var txt=this.statusmsg.format([total,total!=1?'s':'',!count?'none':count==total?'all':count]);
		clearMessage();	displayMessage(txt);
		return txt;
	},
	refreshList: function(selectedIndex) {
		var theList = this.$('exportList'); if (!theList) return;
		// get the sort order
		var sort;
		if (!selectedIndex)   selectedIndex=0;
		if (selectedIndex==0) sort='modified';
		if (selectedIndex==1) sort='title';
		if (selectedIndex==2) sort='modified';
		if (selectedIndex==3) sort='modifier';
		if (selectedIndex==4) sort='tags';

		// unselect headings and count number of tiddlers actually selected
		var count=0;
		for (var t=5; t < theList.options.length; t++) {
			if (!theList.options[t].selected) continue;
			if (theList.options[t].value!='')
				count++;
			else { // if heading is selected, deselect it, and then select and count all in section
				theList.options[t].selected=false;
				for ( t++; t<theList.options.length && theList.options[t].value!=''; t++) {
					theList.options[t].selected=true;
					count++;
				}
			}
		}

		// disable 'export' and 'delete' buttons if no tiddlers selected
		this.$('exportStart').disabled=(count==0);
		this.$('exportDelete').disabled=(count==0);

		// show selection count
		var tiddlers = store.getTiddlers('title');
		if (theList.options.length) this.displayStatus(count,tiddlers.length);

		// if a [command] item, reload list... otherwise, no further refresh needed
		if (selectedIndex>4) return;

		// clear current list contents
		while (theList.length > 0) { theList.options[0] = null; }
		// add heading and control items to list
		var i=0;
		var indent=String.fromCharCode(160)+String.fromCharCode(160);
		theList.options[i++]=
			new Option(tiddlers.length+' tiddlers in document', '',false,false);
		theList.options[i++]=
			new Option(((sort=='title'   )?'>':indent)+' [by title]', '',false,false);
		theList.options[i++]=
			new Option(((sort=='modified')?'>':indent)+' [by date]', '',false,false);
		theList.options[i++]=
			new Option(((sort=='modifier')?'>':indent)+' [by author]', '',false,false);
		theList.options[i++]=
			new Option(((sort=='tags'    )?'>':indent)+' [by tags]', '',false,false);

		// output the tiddler list
		switch(sort) {
			case 'title':
				for(var t = 0; t < tiddlers.length; t++)
					theList.options[i++] = new Option(tiddlers[t].title,tiddlers[t].title,false,false);
				break;
			case 'modifier':
			case 'modified':
				var tiddlers = store.getTiddlers(sort);
				// sort descending for newest date first
				tiddlers.sort(function (a,b) {if(a[sort] == b[sort]) return(0); else return (a[sort] > b[sort]) ? -1 : +1; });
				var lastSection = '';
				for(var t = 0; t < tiddlers.length; t++) {
					var tiddler = tiddlers[t];
					var theSection = '';
					if (sort=='modified') theSection=tiddler.modified.toLocaleDateString();
					if (sort=='modifier') theSection=tiddler.modifier;
					if (theSection != lastSection) {
						theList.options[i++] = new Option(theSection,'',false,false);
						lastSection = theSection;
					}
					theList.options[i++] = new Option(indent+indent+tiddler.title,tiddler.title,false,false);
				}
				break;
			case 'tags':
				var theTitles = {}; // all tiddler titles, hash indexed by tag value
				var theTags = new Array();
				for(var t=0; t<tiddlers.length; t++) {
					var title=tiddlers[t].title;
					var tags=tiddlers[t].tags;
					if (!tags || !tags.length) {
						if (theTitles['untagged']==undefined) { theTags.push('untagged'); theTitles['untagged']=new Array(); }
						theTitles['untagged'].push(title);
					}
					else for(var s=0; s<tags.length; s++) {
						if (theTitles[tags[s]]==undefined) { theTags.push(tags[s]); theTitles[tags[s]]=new Array(); }
						theTitles[tags[s]].push(title);
					}
				}
				theTags.sort();
				for(var tagindex=0; tagindex<theTags.length; tagindex++) {
					var theTag=theTags[tagindex];
					theList.options[i++]=new Option(theTag,'',false,false);
					for(var t=0; t<theTitles[theTag].length; t++)
						theList.options[i++]=new Option(indent+indent+theTitles[theTag][t],theTitles[theTag][t],false,false);
				}
				break;
			}
		theList.selectedIndex=selectedIndex; // select current control item
		this.$('exportStart').disabled=true;
		this.$('exportDelete').disabled=true;
		this.displayStatus(0,tiddlers.length);
	},
	askForFilename: function(here) {
		var msg=here.title; // use tooltip as dialog box message
		var path=getLocalPath(document.location.href);
		var slashpos=path.lastIndexOf('/'); if (slashpos==-1) slashpos=path.lastIndexOf('\\'); 
		if (slashpos!=-1) path = path.substr(0,slashpos+1); // remove filename from path, leave the trailing slash
		var filetype=this.$('exportFormat').value.toLowerCase();
		var defext='html';
		if (filetype==this.type_TX) defext='txt';
		if (filetype==this.type_CS) defext='csv';
		if (filetype==this.type_NF) defext='xml';
		var file=this.newdefault.replace(/html$/,defext);
		var result='';
		if(window.Components) { // moz
			try {
				netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
				var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
				var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
				picker.init(window, msg, nsIFilePicker.modeSave);
				var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
				thispath.initWithPath(path);
				picker.displayDirectory=thispath;
				picker.defaultExtension=defext;
				picker.defaultString=file;
				picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
				if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.path;
			}
			catch(e) { alert('error during local file access: '+e.toString()) }
		}
		else { // IE
			try { // XPSP2 IE only
				var s = new ActiveXObject('UserAccounts.CommonDialog');
				s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|XML files|*.xml|';
				s.FilterIndex=defext=='txt'?2:'html'?3:'xml'?4:1;
				s.InitialDir=path;
				s.FileName=file;
				if (s.showOpen()) var result=s.FileName;
			}
			catch(e) {  // fallback
				var result=prompt(msg,path+file);
			}
		}
		return result;
	},
	initFilter: function() {
		this.$('exportFilterStart').checked=false; this.$('exportStartDate').value='';
		this.$('exportFilterEnd').checked=false;  this.$('exportEndDate').value='';
		this.$('exportFilterTags').checked=false; this.$('exportTags').value='';
		this.$('exportFilterText').checked=false; this.$('exportText').value='';
		this.showFilterFields();
	},
	showFilterFields: function(which) {
		var show=this.$('exportFilterStart').checked;
		this.$('exportFilterStartBy').style.display=show?'block':'none';
		this.$('exportStartDate').style.display=show?'block':'none';
		var val=this.$('exportFilterStartBy').value;
		this.$('exportStartDate').value
			=this.getFilterDate(val,'exportStartDate').formatString(this.datetimefmt);
		if (which && (which.id=='exportFilterStartBy') && (val=='other'))
			this.$('exportStartDate').focus();

		var show=this.$('exportFilterEnd').checked;
		this.$('exportFilterEndBy').style.display=show?'block':'none';
		this.$('exportEndDate').style.display=show?'block':'none';
		var val=this.$('exportFilterEndBy').value;
		this.$('exportEndDate').value
			=this.getFilterDate(val,'exportEndDate').formatString(this.datetimefmt);
		 if (which && (which.id=='exportFilterEndBy') && (val=='other'))
			this.$('exportEndDate').focus();

		var show=this.$('exportFilterTags').checked;
		this.$('exportTags').style.display=show?'block':'none';

		var show=this.$('exportFilterText').checked;
		this.$('exportText').style.display=show?'block':'none';
	},
	getFilterDate: function(val,id) {
		var result=0;
		switch (val) {
			case 'file':
				result=new Date(document.lastModified);
				break;
			case 'other':
				result=new Date(this.$(id).value);
				break;
			default: // today=0, yesterday=1, one week=7, two weeks=14, a month=31
				var now=new Date(); var tz=now.getTimezoneOffset()*60000; now-=tz;
				var oneday=86400000;
				if (id=='exportStartDate')
					result=new Date((Math.floor(now/oneday)-val)*oneday+tz);
				else
					result=new Date((Math.floor(now/oneday)-val+1)*oneday+tz-1);
				break;
		}
		return result;
	},
	filterExportList: function() {
		var theList  = this.$('exportList'); if (!theList) return -1;
		var filterStart=this.$('exportFilterStart').checked;
		var val=this.$('exportFilterStartBy').value;
		var startDate=config.macros.exportTiddlers.getFilterDate(val,'exportStartDate');
		var filterEnd=this.$('exportFilterEnd').checked;
		var val=this.$('exportFilterEndBy').value;
		var endDate=config.macros.exportTiddlers.getFilterDate(val,'exportEndDate');
		var filterTags=this.$('exportFilterTags').checked;
		var tags=this.$('exportTags').value;
		var filterText=this.$('exportFilterText').checked;
		var text=this.$('exportText').value;
		if (!(filterStart||filterEnd||filterTags||filterText)) {
			alert('Please set the selection filter');
			this.$('exportFilterPanel').style.display='block';
			return -1;
		}
		if (filterStart&&filterEnd&&(startDate>endDate)) {
			var msg='starting date/time:\n'
			msg+=startDate.toLocaleString()+'\n';
			msg+='is later than ending date/time:\n'
			msg+=endDate.toLocaleString()
			alert(msg);
			return -1;
		}
		// if filter by tags, get list of matching tiddlers
		// use getMatchingTiddlers() (if MatchTagsPlugin is installed) for full boolean expressions
		// otherwise use getTaggedTiddlers() for simple tag matching
		if (filterTags) {
			var fn=store.getMatchingTiddlers||store.getTaggedTiddlers;
			var t=fn.apply(store,[tags]);
			var tagged=[];
			for (var i=0; i<t.length; i++) tagged.push(t[i].title);
		}
		// scan list and select tiddlers that match all applicable criteria
		var total=0;
		var count=0;
		for (var i=0; i<theList.options.length; i++) {
			// get item, skip non-tiddler list items (section headings)
			var opt=theList.options[i]; if (opt.value=='') continue;
			// get tiddler, skip missing tiddlers (this should NOT happen)
			var tiddler=store.getTiddler(opt.value); if (!tiddler) continue; 
			var sel=true;
			if ( (filterStart && tiddler.modified<startDate)
			|| (filterEnd && tiddler.modified>endDate)
			|| (filterTags && !tagged.contains(tiddler.title))
			|| (filterText && (tiddler.text.indexOf(text)==-1) && (tiddler.title.indexOf(text)==-1)))
				sel=false;
			opt.selected=sel;
			count+=sel?1:0;
			total++;
		}
		return count;
	},
	deleteTiddlers: function() {
		var list=this.$('exportList'); if (!list) return;
		var tids=[];
		for (i=0;i<list.length;i++)
			if (list.options[i].selected && list.options[i].value.length)
				tids.push(list.options[i].value);
		if (!confirm('Are you sure you want to delete these tiddlers:\n\n'+tids.join(', '))) return;
		store.suspendNotifications();
		for (t=0;t<tids.length;t++) {
			var tid=store.getTiddler(tids[t]); if (!tid) continue;
			var msg="'"+tid.title+"' is tagged with 'systemConfig'.\n\n";
			msg+='Removing this tiddler may cause unexpected results.  Are you sure?'
			if (tid.tags.contains('systemConfig') && !confirm(msg)) continue;
			store.removeTiddler(tid.title);
			story.closeTiddler(tid.title);
		}
		store.resumeNotifications();
		alert(tids.length+' tiddlers deleted');
		this.refreshList(0); // reload listbox
		store.notifyAll(); // update page display
	},
	go: function() {
		if (window.location.protocol!='file:') // make sure we are local
			{ displayMessage(config.messages.notFileUrlError); return; }
		// get selected tidders, target filename, target type, and notes
		var list=this.$('exportList'); if (!list) return;
		var tids=[]; for (var i=0; i<list.options.length; i++) {
			var opt=list.options[i]; if (!opt.selected||!opt.value.length) continue;
			var tid=store.getTiddler(opt.value); if (!tid) continue;
			tids.push(tid);
		}
		if (!tids.length) return; // no tiddlers selected
		var target=this.$('exportFilename').value.trim();
		if (!target.length) {
			displayMessage('A local target path/filename is required',target);
			return;
		}
		var merge=this.$('exportMerge').checked;
		var filetype=this.$('exportFormat').value.toLowerCase();
		var notes=this.$('exportNotes').value.replace(/\n/g,'<br>');
		var total={val:0};
		var out=this.assembleFile(target,filetype,tids,notes,total,merge);
		if (!total.val) return; // cancelled file overwrite
		var link='file:///'+target.replace(/\\/g,'/');
		var samefile=link==decodeURIComponent(window.location.href);
		var p=getLocalPath(document.location.href);
		if (samefile) {
			if (config.options.chkSaveBackups) { var t=loadOriginal(p);if(t)saveBackup(p,t); }
			if (config.options.chkGenerateAnRssFeed && saveRss instanceof Function) saveRss(p);
		}
		var ok=saveFile(target,out);
		displayMessage((ok?this.okmsg:this.failmsg).format([total.val,total.val!=1?'s':'',target]),link);
	},
	plainTextHeader:
		 'Source:\n\t%0\n'
		+'Title:\n\t%1\n'
		+'Subtitle:\n\t%2\n'
		+'Created:\n\t%3 by %4\n'
		+'Application:\n\tTiddlyWiki %5 / %6 %7\n\n',
	plainTextTiddler:
		'- - - - - - - - - - - - - - -\n'
		+'|     title: %0\n'
		+'|   created: %1\n'
		+'|  modified: %2\n'
		+'| edited by: %3\n'
		+'|      tags: %4\n'
		+'- - - - - - - - - - - - - - -\n'
		+'%5\n',
	plainTextFooter:
		'',
	newsFeedHeader:
		 '<'+'?xml version="1.0"?'+'>\n'
		+'<rss version="2.0">\n'
		+'<channel>\n'
		+'<title>%1</title>\n'
		+'<link>%0</link>\n'
		+'<description>%2</description>\n'
		+'<language>en-us</language>\n'
		+'<copyright>Copyright '+(new Date().getFullYear())+' %4</copyright>\n'
		+'<pubDate>%3</pubDate>\n'
		+'<lastBuildDate>%3</lastBuildDate>\n'
		+'<docs>http://blogs.law.harvard.edu/tech/rss</docs>\n'
		+'<generator>TiddlyWiki %5 / %6 %7</generator>\n',
	newsFeedTiddler:
		'\n%0\n',
	newsFeedFooter:
		'</channel></rss>',
	pureStoreHeader:
		 '<html><body>'
		+'<style type="text/css">'
		+'	#storeArea {display:block;margin:1em;}'
		+'	#storeArea div {padding:0.5em;margin:1em;border:2px solid black;height:10em;overflow:auto;}'
		+'	#pureStoreHeading {width:100%;text-align:left;background-color:#eeeeee;padding:1em;}'
		+'</style>'
		+'<div id="pureStoreHeading">'
		+'	TiddlyWiki "PureStore" export file<br>'
		+'	Source'+': <b>%0</b><br>'
		+'	Title: <b>%1</b><br>'
		+'	Subtitle: <b>%2</b><br>'
		+'	Created: <b>%3</b> by <b>%4</b><br>'
		+'	TiddlyWiki %5 / %6 %7<br>'
		+'	Notes:<hr><pre>%8</pre>'
		+'</div>'
		+'<div id="storeArea">',
	pureStoreTiddler:
		'%0\n%1',
	pureStoreFooter:
		'</div><!--POST-BODY-START-->\n<!--POST-BODY-END--></body></html>',
	assembleFile: function(target,filetype,tids,notes,total,merge) {
		var revised='';
		var now = new Date().toLocaleString();
		var src=convertUnicodeToUTF8(document.location.href);
		var title = convertUnicodeToUTF8(wikifyPlain('SiteTitle').htmlEncode());
		var subtitle = convertUnicodeToUTF8(wikifyPlain('SiteSubtitle').htmlEncode());
		var user = convertUnicodeToUTF8(config.options.txtUserName.htmlEncode());
		var twver = version.major+'.'+version.minor+'.'+version.revision;
		var v=version.extensions.ExportTiddlersPlugin; var pver = v.major+'.'+v.minor+'.'+v.revision;
		var headerargs=[src,title,subtitle,now,user,twver,'ExportTiddlersPlugin',pver,notes];
		switch (filetype) {
			case this.type_TX: // plain text
				var header=this.plainTextHeader.format(headerargs);
				var footer=this.plainTextFooter;
				break;
			case this.type_CS: // comma-separated
				var fields={};
				for (var i=0; i<tids.length; i++) for (var f in tids[i].fields) fields[f]=f;
				var names=['title','created','modified','modifier','tags','text'];
				for (var f in fields) names.push(f);
				var header=names.join(',')+'\n';
				var footer='';
				break;
			case this.type_NF: // news feed (XML)
				headerargs[0]=store.getTiddlerText('SiteUrl','');
				var header=this.newsFeedHeader.format(headerargs);
				var footer=this.newsFeedFooter;
				break;
			case this.type_PS: // PureStore (no code)
				var header=this.pureStoreHeader.format(headerargs);
				var footer=this.pureStoreFooter;
				break;
			case this.type_TW: // full TiddlyWiki
			default:
				var currPath=getLocalPath(window.location.href);
				var original=loadFile(currPath);
				if (!original) { displayMessage(config.messages.cantSaveError); return; }
				var posDiv = locateStoreArea(original);
				if (!posDiv) { displayMessage(config.messages.invalidFileError.format([currPath])); return; }
				var header = original.substr(0,posDiv[0]+startSaveArea.length)+'\n';
				var footer = '\n'+original.substr(posDiv[1]);
				break;
		}
		var out=this.getData(target,filetype,tids,fields,merge);
		var revised = header+convertUnicodeToUTF8(out.join('\n'))+footer;
		// if full TW, insert page title and language attr, and reset all MARKUP blocks...
		if (filetype==this.type_TW) {
			var newSiteTitle=convertUnicodeToUTF8(getPageTitle()).htmlEncode();
			revised=revised.replaceChunk('<title'+'>','</title'+'>',' ' + newSiteTitle + ' ');
			revised=updateLanguageAttribute(revised);
			var titles=[]; for (var i=0; i<tids.length; i++) titles.push(tids[i].title);
			revised=updateMarkupBlock(revised,'PRE-HEAD',
				titles.contains('MarkupPreHead')? 'MarkupPreHead' :null);
			revised=updateMarkupBlock(revised,'POST-HEAD',
				titles.contains('MarkupPostHead')?'MarkupPostHead':null);
			revised=updateMarkupBlock(revised,'PRE-BODY',
				titles.contains('MarkupPreBody')? 'MarkupPreBody' :null);
			revised=updateMarkupBlock(revised,'POST-SCRIPT',
				titles.contains('MarkupPostBody')?'MarkupPostBody':null);
		}
		total.val=out.length;
		return revised;
	},
	getData: function(target,filetype,tids,fields,merge) {
		// output selected tiddlers and gather list of titles (for use with merge)
		var out=[]; var titles=[];
		var url=store.getTiddlerText('SiteUrl','');
		for (var i=0; i<tids.length; i++) {
			out.push(this.formatItem(store,filetype,tids[i],url,fields));
			titles.push(tids[i].title);
		}
		// if TW or PureStore format, ask to merge with existing tiddlers (if any)
		if (filetype==this.type_TW || filetype==this.type_PS) {
			var txt=loadFile(target);
			if (txt && txt.length) {
				var remoteStore=new TiddlyWiki();
				if (version.major+version.minor*.1+version.revision*.01<2.52) txt=convertUTF8ToUnicode(txt);
				if (remoteStore.importTiddlyWiki(txt)) {
					var existing=remoteStore.getTiddlers('title');
					var msg=this.overwriteprompt.format([target,existing.length,existing.length!=1?'s':'']);
					if (merge) {
						var added=titles.length; var updated=0; var kept=0;
						for (var i=0; i<existing.length; i++)
							if (titles.contains(existing[i].title)) {
								added--; updated++;
							} else {
								out.push(this.formatItem(remoteStore,filetype,existing[i],url));
								kept++;
							}
						displayMessage(this.mergestatus.format(
							[added,added!=1?'s':'',updated,updated!=1?'s':'',kept,kept!=1?'s':'',]));
					}
					else if (!confirm(msg)) out=[]; // empty the list = don't write file
				}
			}
		}
		return out;
	},
	formatItem: function(s,f,t,u,fields) {
		if (f==this.type_TW)
			var r=s.getSaver().externalizeTiddler(s,t);
		if (f==this.type_PS)
			var r=this.pureStoreTiddler.format([t.title,s.getSaver().externalizeTiddler(s,t)]);
		if (f==this.type_NF)
			var r=this.newsFeedTiddler.format([t.saveToRss(u)]);
		if (f==this.type_TX)
			var r=this.plainTextTiddler.format([t.title, t.created.toLocaleString(), t.modified.toLocaleString(),
				t.modifier, String.encodeTiddlyLinkList(t.tags), t.text]);
		if (f==this.type_CS) {
			function toCSV(t) { return '"'+t.replace(/"/g,'""')+'"'; } // always encode CSV
			var out=[ toCSV(t.title), toCSV(t.created.toLocaleString()), toCSV(t.modified.toLocaleString()),
				toCSV(t.modifier), toCSV(String.encodeTiddlyLinkList(t.tags)), toCSV(t.text) ];
			for (var f in fields) out.push(toCSV(t.fields[f]||''));
			var r=out.join(',');
		}
		return r||"";
	}
}
//}}}
/***
!!!Control panel CSS
//{{{
!css
#exportPanel {
	display: none; position:absolute; z-index:12; width:35em; right:105%; top:6em;
	background-color: #eee; color:#000; font-size: 8pt; line-height:110%;
	border:1px solid black; border-bottom-width: 3px; border-right-width: 3px;
	padding: 0.5em; margin:0em; -moz-border-radius:1em;-webkit-border-radius:1em;
}
#exportPanel a, #exportPanel td a { color:#009; display:inline; margin:0px; padding:1px; }
#exportPanel table {
	width:100%; border:0px; padding:0px; margin:0px;
	font-size:8pt; line-height:110%; background:transparent;
}
#exportPanel tr { border:0px;padding:0px;margin:0px; background:transparent; }
#exportPanel td { color:#000; border:0px;padding:0px;margin:0px; background:transparent; }
#exportPanel select { width:98%;margin:0px;font-size:8pt;line-height:110%;}
#exportPanel input  { width:98%;padding:0px;margin:0px;font-size:8pt;line-height:110%; }
#exportPanel textarea  { width:98%;padding:0px;margin:0px;overflow:auto;font-size:8pt; }
#exportPanel .box {
	border:1px solid black; padding:3px; margin-bottom:5px;
	background:#f8f8f8; -moz-border-radius:5px;-webkit-border-radius:5px; }
#exportPanel .topline { border-top:2px solid black; padding-top:3px; margin-bottom:5px; }
#exportPanel .rad { width:auto;border:0 }
#exportPanel .chk { width:auto;border:0 }
#exportPanel .btn { width:auto; }
#exportPanel .btn1 { width:98%; }
#exportPanel .btn2 { width:48%; }
#exportPanel .btn3 { width:32%; }
#exportPanel .btn4 { width:24%; }
#exportPanel .btn5 { width:19%; }
!end
//}}}
!!!Control panel HTML
//{{{
!html
<!-- target path/file  -->
<div>
<div style="float:right;padding-right:.5em">
<input type="checkbox" style="width:auto" id="exportMerge" CHECKED
	title="combine selected tiddlers with existing tiddlers (if any) in export file"> merge
</div>
export to:<br>
<input type="text" id="exportFilename" size=40 style="width:93%"><input 
	type="button" id="exportBrowse" value="..." title="select or enter a local folder/file..." style="width:5%" 
	onclick="var fn=config.macros.exportTiddlers.askForFilename(this); if (fn.length) this.previousSibling.value=fn; ">
</div>

<!-- output format -->
<div>
format:
<select id="exportFormat" size=1>
	<option value="TW">TiddlyWiki HTML document (includes core code)</option>
	<option value="PS">TiddlyWiki "PureStore" HTML file (tiddler data only)</option>
	<option value="TX">TiddlyWiki plain text TXT file (tiddler source listing)</option>
	<option value="CS">Comma-Separated Value (CSV) data file</option>
	<option value="NF">RSS NewsFeed XML file</option>
</select>
</div>

<!-- notes -->
<div>
notes:<br>
<textarea id="exportNotes" rows=3 cols=40 style="height:4em;margin-bottom:5px;" onfocus="this.select()"></textarea> 
</div>

<!-- list of tiddlers -->
<table><tr align="left"><td>
	select:
	<a href="JavaScript:;" id="exportSelectAll"
		onclick="return config.macros.exportTiddlers.process(this)" title="select all tiddlers">
		&nbsp;all&nbsp;</a>
	<a href="JavaScript:;" id="exportSelectChanges"
		onclick="return config.macros.exportTiddlers.process(this)" title="select tiddlers changed since last save">
		&nbsp;changes&nbsp;</a>
	<a href="JavaScript:;" id="exportSelectOpened"
		onclick="return config.macros.exportTiddlers.process(this)" title="select tiddlers currently being displayed">
		&nbsp;opened&nbsp;</a>
	<a href="JavaScript:;" id="exportSelectRelated"
		onclick="return config.macros.exportTiddlers.process(this)" title="select tiddlers related to the currently selected tiddlers">
		&nbsp;related&nbsp;</a>
	<a href="JavaScript:;" id="exportToggleFilter"
		onclick="return config.macros.exportTiddlers.process(this)" title="show/hide selection filter">
		&nbsp;filter&nbsp;</a>
</td><td align="right">
	<a href="JavaScript:;" id="exportListSmaller"
		onclick="return config.macros.exportTiddlers.process(this)" title="reduce list size">
		&nbsp;&#150;&nbsp;</a>
	<a href="JavaScript:;" id="exportListLarger"
		onclick="return config.macros.exportTiddlers.process(this)" title="increase list size">
		&nbsp;+&nbsp;</a>
</td></tr></table>
<select id="exportList" multiple size="10" style="margin-bottom:5px;"
	onchange="config.macros.exportTiddlers.refreshList(this.selectedIndex)">
</select><br>

<!-- selection filter -->
<div id="exportFilterPanel" style="display:none">
<table><tr align="left"><td>
	selection filter
</td><td align="right">
	<a href="JavaScript:;" id="exportHideFilter"
		onclick="return config.macros.exportTiddlers.process(this)" title="hide selection filter">hide</a>
</td></tr></table>
<div class="box">

<input type="checkbox" class="chk" id="exportFilterStart" value="1"
	onclick="config.macros.exportTiddlers.showFilterFields(this)"> starting date/time<br>
<table cellpadding="0" cellspacing="0"><tr valign="center"><td width="50%">
	<select size=1 id="exportFilterStartBy"
		onchange="config.macros.exportTiddlers.showFilterFields(this);">
		<option value="0">today</option>
		<option value="1">yesterday</option>
		<option value="7">a week ago</option>
		<option value="30">a month ago</option>
		<option value="file">file date</option>
		<option value="other">other (mm/dd/yyyy hh:mm)</option>
	</select>
</td><td width="50%">
	<input type="text" id="exportStartDate" onfocus="this.select()"
		onchange="config.macros.exportTiddlers.$('exportFilterStartBy').value='other';">
</td></tr></table>

<input type="checkbox" class="chk" id="exportFilterEnd" value="1"
	onclick="config.macros.exportTiddlers.showFilterFields(this)"> ending date/time<br>
<table cellpadding="0" cellspacing="0"><tr valign="center"><td width="50%">
	<select size=1 id="exportFilterEndBy"
		onchange="config.macros.exportTiddlers.showFilterFields(this);">
		<option value="0">today</option>
		<option value="1">yesterday</option>
		<option value="7">a week ago</option>
		<option value="30">a month ago</option>
		<option value="file">file date</option>
		<option value="other">other (mm/dd/yyyy hh:mm)</option>
	</select>
</td><td width="50%">
	<input type="text" id="exportEndDate" onfocus="this.select()"
		onchange="config.macros.exportTiddlers.$('exportFilterEndBy').value='other';">
</td></tr></table>

<input type="checkbox" class="chk" id=exportFilterTags value="1"
	onclick="config.macros.exportTiddlers.showFilterFields(this)"> match tags<br>
<input type="text" id="exportTags" onfocus="this.select()">

<input type="checkbox" class="chk" id=exportFilterText value="1"
	onclick="config.macros.exportTiddlers.showFilterFields(this)"> match titles/tiddler text<br>
<input type="text" id="exportText" onfocus="this.select()">

</div> <!--box-->
</div> <!--panel-->

<!-- action buttons -->
<div style="text-align:center">
<input type=button class="btn4" onclick="config.macros.exportTiddlers.process(this)"
	id="exportFilter" value="apply filter">
<input type=button class="btn4" onclick="config.macros.exportTiddlers.process(this)"
	id="exportStart" value="export tiddlers">
<input type=button class="btn4" onclick="config.macros.exportTiddlers.process(this)"
	id="exportDelete" value="delete tiddlers">
<input type=button class="btn4" onclick="config.macros.exportTiddlers.process(this)"
	id="exportClose" value="close">
</div><!--center-->
!end
//}}}
***/
 
Several features are being considered for future releases:
* Automatic pre-send [[SPF]] checks and enforcement
* Dynamic list segmentation
* Always keeping an eye on developments in ~JavaScript WYSIWYG text editors to improve mailshot editing.
* Multi-language support in the customer interface
* Brandable (white-labelling) user interface for agencies (it's already brandable for your subscribers)
* New UI with nested campaign data
[[Google Analytics|http://www.google.com/analytics/]] is a quick and easy way to add tracking for end-to-end marketing feedback. While we track all that we can (opens and clicks), we have no way of knowing how that helps your bottom line. How would you like to be able to see how many people from your mailshots actually bought something from your web site? That's the kind of thing that Google Analytics can tell you.

[IMG[images/google.gif]]

!How it works
When we handle a clickthrough for you (see [[Link Tracking]]), we check if the link is pointing at one of your analytics domains, we add some additional parameters to the clicked URL to let analytics know where the link came from before telling the user's browser where to go. For example, a link to //http&#58;&#47;/www.example.com/// might be extended to //https&#58;&#47;/www.example.com/?utm_source=smartmessages.net&utm_medium=email&utm_campaign=My mailshot&utm_content=12345// that is, we add in information to the link about which mailshot the click is associated with. When the browser gets to the destination site, the Google Analytics javascript spots the additional parameters on the incoming URL and adds it to the information that it logs. It then means that all activity that this particular user does can be linked to the fact that they came from the mailshot.
!How to set it up
There are two parts to setting up analytics in Smartmessages. Firstly you need to go to your Smartmessages accounts settings page, click the 'enable analytics' checkbox, enter the domains that you want to use analytics for (for example 'www.example.com') and then save your settings. After that, all redirected links in that domain will get the additional parameters added. Domains not in your list will not get the additional parameters. The campaign property comes from the name you give your mailshot when you send it, and the content value is our internal ID for the mailshot, which you may find useful if you use our [[API]] (or to help differentiate mailshots that have been given the same name).
The second step is to install Google Analytics on your web site. That's highly dependent on how your site is set up, but [[Google tells you how to do it|http://www.google.com/analytics/discover_analytics.html]]. Once it's done, you should find that you start to see (on the analytics site, not in Smartmessages) a proportion of your visitors as coming from email links, referred by us, ripe for further analysis. It's not a real-time service, so you'll find that there is some delay before stats start coming through.
Gravatars are personalisation images provided through [[gravatar.com|http://www.gravatar.com]], which are commonly used by users of wordpress.com, and many internet forums. This service associates an icon with an email address, and can either be set by the user, or generated algorithmically, so every address gets a unique icon. Here's an example of a custom icon set for info@smartmessages.net:


[<IMG[http://www.gravatar.com/avatar/cb4119cab961918d4d1a0488878042b2.png?s=80]]





and here's a generated one for an address that has no predefined icon:


[<IMG[https://secure.gravatar.com/avatar/f211411290589aea573c916329ec8691?r=pg&d=identicon&s=80]]





We use gravatars in our admin interface, but you can also use them in email templates with the {{{[[gravatar size=16]]}}}, {{{[[gravatar size=32]]}}} and {{{[[gravatar size=64]]}}} template tags which render icons of 16x16, 32x32 or 64x64 icons respectively for the recipient's email address.
!!Optional parameters
The gravatar tag accepts several optional parameters:
|!Parameter |!Purpose |!Default value |
| {{{size}}} |Width and height of the generated icon (they are always square), in pixels|80|
| {{{rating}}} |Audiences that this gravatar is appropriate for, using the American film rating standard: {{{g}}}, {{{pg}}}, {{{r}}}, {{{x}}}|{{{pg}}}|
| {{{email}}} |The email address to generate a gravatar for|The recipient of the email|
| {{{default}}} |The icon style to use if the address does not have a custom gravatar. One of {{{404}}}, {{{mm}}}, {{{identicon}}}, {{{monsterid}}}, {{{wavatar}}}, {{{retro}}}, {{{blank}}}|{{{identicon}}}|
| {{{urlonly}}} |If present, this option will cause this tag to render to the URL of the gravatar image, otherwise it will render to a complete image tag||
The default behaviour is thus the same as saying:
{{{
[[gravatar email=$subscriber.email rating="pg" size=80 default="identicon"]]
}}}
There are several ways of using images in HTML email, in order of popularity:
* Attached images
* Linked images
* Embedded images
* Data ~URIs
* No images!
In short, we recommend that you use linked images, and that's the only approach that Smartmessages supports anyway! For the full technical rundown on why that is, read on.
!!Attached images
The most common way that any kind of files (including images) are used in email is as simple attachments - this is what usually happens when your Mum sends you her holiday pics, or a colleague sends you a Word document. Many email clients (Outlook, Apple Mail etc) will display these with the message, but there isn't any particular layout relationship between the message content and the images, they are just lumped together at the bottom of the message. Because the image data is sent with the message, messages tend to be large, occupying space on the receiver's mail server even if the recipient does not open the message. This is great if you're only sending files to a few people, but is very inefficient for more than that, especially if your open rate is low.
Within the message structure, the image data is encoded using a system called [[base64|http://en.wikipedia.org/wiki/Base64]] which allows the image to be sent safely through a communications system designed for text (i.e. email). A base64-encoded image attachment in an email message looks like this:
{{{
Content-Type: image/gif; name="pixel.gif"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=pixel.gif

R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
}}}
Believe it or not, that is a transparent 1-pixel [[GIF-format|http://en.wikipedia.org/wiki/GIF]] image.
!!!Client support
Image attachments are supported by all email clients worth mentioning.
!!Linked images
Next most common is simple linked images. These work just like images on web pages, and the HTML markup is identical, for example:
{{{
<img src="http://www.smartmessages.net/images/smlogo256dk.png" alt="smartmessages logo" width="256" height="27"> 
}}}
The advantage of these is that the image data is not sent with the message, keeping the message size small; the image is only loaded when the message is opened. They can be placed in HTML layouts with precise control over size and position.
In email clients, the fact that these are loaded when a message is opened can be used to detect when a message has been opened, and Smartmessages uses exactly this mechanism. This is also why most email clients default to not displaying images, to improve default privacy, and to speed loading and display of the message, particularly on mobile devices where data connections may be very slow and possibly expensive for the recipient. If the images are very small (e.g. for bullet images or icons), the overhead of a round-trip to the server (in both time and data volume) can negate this advantage. An email client may use a cache much like a web browser does, so if you send a message with your logo in, the second time they receive a message from you they may not need to download your logo because they can use the cached copy they loaded last time.
!!!Client support
Image attachments are supported by all email clients worth mentioning, though images are likely to be hidden at least the first time you send to them.
!!Embedded images
This is a cross between the first two. When an image is attached to a message, it can be assigned an identifier (using a format that often looks similar to an email address) within the message structure like this:
{{{
Content-Type: image/gif; name="pixel.gif"
Content-Transfer-Encoding: base64
Content-ID: <12345@example.net>
Content-Disposition: inline; filename=pixel.gif

R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
}}}
This allows an HTML layout within the same message to reference the image using similar markup to linked images, like this:
{{{
<img src="cid:12345@example.net" alt="pixel" width="1" height="1"> 
}}}
You can see that the same content-id is assigned to the part of the message that contains the image attachment, and so the email client program knows to look in that named part to find the image data to display in the HTML layout.
This approach has all the downsides of normal attached images (particularly large message sizes), but gains control over layout as it is displayed via the HTML markup. It's quite common for embedded or attched images to be enabled by default (as they pose no threat to privacy), and because the image data is already loaded, the layout is usually displayed instantly in all its glory.
An advantage of this approach is that the HTML can make multiple references to the same content identifier, so if for example you have a logo image that appears in three places in the message, it only has to store a single copy of the actual data.
!!!Client support
Embedded images are supported by all email clients worth mentioning.
!!Data ~URIs
[[Data URIs|http://en.wikipedia.org/wiki/Data_Uri]] work in a very similar way to the previous type; the difference is where the image data is stored. A data URI embeds the image data directly into the URI itself, so our example HTML would look like this (using the same image data as the previous example):
{{{
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="pixel!" width="1" height="1"> 
}}}
This can work in email, but it has a number of major downsides - it has the same disadvantages as attached images, but because there is no identifier associated with the image data, it can't be referred to by another element - if you wanted the same image to appear 3 times, it would embed three copies of the image data. Data ~URIs are a relatively recent development; they are not supported at all in Internet Explorer 5-7, and IE 8 is limited to 32k of data, which is enough for a small logo or icon, but useless for photographs.
Data ~URIs are most often used in web pages to save round-trips to the server to load small images, and there is no real point in using them in email - if you want to include image data in your messages you're better off with embedded images as they are more compatible and allow for larger images.
!!!Client support
Data ~URIs have very patchy support. Outlook is particularly bad; data-uri images are not displayed when images are disabled, but they are not displayed if you then ask it to show images either!
!!No images!
As you will have gathered, there are tradeoffs in all of the above approaches. We mentioned that image display is usually disabled in email clients, so your carefull-crafted images often won't be displayed anyway. With the increasing use of mobile devices on slow networks, not using images at all has considerable advantages - messages go fast, recipients get messages quickly and see them exactly as you intended (i.e. not missing images!). It's possible to produce good looking messages using only fonts and solid colours (gradients in some cases) that avoid all of the above pitfalls. Of course, it may not be right for you - it wouldn't be much good for presenting your photo portfolio, but it might be perfect for a sale announcement. Like many things, it's usually good to aim for a compromise - make a message that looks good with no images, and add a couple of images that make it look great, but built so that the meaning and general impression is not lost if they are missing. 
!!!Testing without images
Smartmessages lets you check how your messages look with images disabled - on the template and send pages, select "Preview (Images off)" from the preview menu.
!Data volume
Message size can make a difference to send speed and resource consumption for both sending and receiving. Here's a calculation assuming we're sending a mailshot of a 10k message using 90k of images to 100,000 subscribers, and we assume that 10% of recipients open the message. For image attachments and data URI approaches, we would need to send a total of:
{{{
(10,000 + 90,000) * 100,000 = 10,000,000,000 bytes = 10 gigabytes of data.
}}}
and all of that data must be sent regardless of whether or when messages are actually received or opened.
The linked image approach would result in a total of
{{{
(10,000 * 100,000) + (90,000 * 10,000) = 1,900,000,000 bytes = approx 2 gigabytes of data.
}}}
So the linked image approach allows sending to go 10 times faster (because about half of the data volume is only incurred after sending)  and consume 1/5 of the storage space. That's good news for both us //and// your subscribers!
/***
|Name|ImportTiddlersPlugin|
|Source|http://www.TiddlyTools.com/#ImportTiddlersPlugin|
|Documentation|http://www.TiddlyTools.com/#ImportTiddlersPluginInfo|
|Version|4.6.2|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|interactive controls for import/export with filtering.|
Combine tiddlers from any two TiddlyWiki documents.  Interactively select and copy tiddlers from another TiddlyWiki source document.  Includes prompting for skip, rename, merge or replace actions when importing tiddlers that match existing titles.  When done, a list of all imported tiddlers is written into [[ImportedTiddlers]].
!!!!!Documentation
<<<
see [[ImportTiddlersPluginInfo]] for details
<<<
!!!!!interactive control panel
<<<
<<importTiddlers inline>>
{{clear{
^^(see also: [[ImportTiddlers]] shadow tiddler)^^}}}
<<<
!!!!!Revisions
<<<
2011.02.14 4.6.2 fix OSX error: use picker.file.path
2009.10.10 4.6.1 in createImportPanel, Use {{{window.Components}}} instead of {{{config.browser.isGecko}}} to avoid applying FF3 'file browse' fixup in Chrome.
2009.10.06 4.6.0 added createTiddlerFromFile (import text files)
|please see [[ImportTiddlersPluginInfo]] for additional revision details|
2005.07.20 1.0.0 Initial Release
<<<
!!!!!Code
***/
//{{{
version.extensions.ImportTiddlersPlugin= {major: 4, minor: 6, revision: 2, date: new Date(2011,2,14)};

// IE needs explicit global scoping for functions/vars called from browser events
window.onClickImportButton=onClickImportButton;
window.refreshImportList=refreshImportList;

// default cookie/option values
if (!config.options.chkImportReport) config.options.chkImportReport=true;

// default shadow definition
config.shadowTiddlers.ImportTiddlers='<<importTiddlers inline>>';

// use shadow tiddler content in backstage panel
if (config.tasks) config.tasks.importTask.content='<<tiddler ImportTiddlers>>' // TW2.2 or above
//}}}
//{{{
// backward-compatiblity for TW2.0.x and TW1.2.x
if (config.macros.importTiddlers==undefined) config.macros.importTiddlers={};
if (typeof merge=='undefined') {
	function merge(dst,src,preserveExisting) {
		for(var i in src) { if(!preserveExisting || dst[i] === undefined) dst[i] = src[i]; }
		return dst;
	}
}
if (config.browser.isGecko===undefined)
	config.browser.isGecko=(config.userAgent.indexOf('gecko')!=-1);
//}}}
//{{{
merge(config.macros.importTiddlers,{
	$: function(id) { return document.getElementById(id); }, // abbreviation
	label: 'import tiddlers',
	prompt: 'Copy tiddlers from another document',
	openMsg: 'Opening %0',
	openErrMsg: 'Could not open %0 - error=%1',
	readMsg: 'Read %0 bytes from %1',
	foundMsg: 'Found %0 tiddlers in %1',
	filterMsg: "Filtered %0 tiddlers matching '%1'",
	summaryMsg: '%0 tiddler%1 in the list',
	summaryFilteredMsg: '%0 of %1 tiddler%2 in the list',
	plural: 's are',
	single: ' is',
	countMsg: '%0 tiddlers selected for import',
	processedMsg: 'Processed %0 tiddlers',
	importedMsg: 'Imported %0 of %1 tiddlers from %2',
	loadText: 'please load a document...',
	closeText: 'close',
	doneText: 'done',
	startText: 'import',
	stopText: 'stop',
	local: true,		// default to import from local file
	src: '',		// path/filename or URL of document to import (retrieved from SiteUrl)
	proxy: '',		// URL for remote proxy script (retrieved from SiteProxy)
	useProxy: false,	// use specific proxy script in front of remote URL
	inbound: null,		// hash-indexed array of tiddlers from other document
	newTags: '',		// text of tags added to imported tiddlers
	addTags: true,		// add new tags to imported tiddlers
	listsize: 10,		// # of lines to show in imported tiddler list
	importTags: true,	// include tags from remote source document when importing a tiddler
	keepTags: true,		// retain existing tags when replacing a tiddler
	sync: false,		// add 'server' fields to imported tiddlers (for sync function)
	lastFilter: '',		// most recent filter (URL hash) applied
	lastAction: null,	// most recent collision button performed
	index: 0,		// current processing index in import list
	sort: ''		// sort order for imported tiddler listbox
});
//}}}
//{{{
// hijack core macro handler
if (config.macros.importTiddlers.coreHandler==undefined)
	config.macros.importTiddlers.coreHandler=config.macros.importTiddlers.handler;

config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	if (!params[0] || params[0].toLowerCase()=='core') { // default to built in
		if (config.macros.importTiddlers.coreHandler)
			config.macros.importTiddlers.coreHandler.apply(this,arguments);
		else 
			createTiddlyButton(place,this.label,this.prompt,onClickImportMenu);
	} else if (params[0]=='link') { // show link to floating panel
		createTiddlyButton(place,params[1]||this.label,params[2]||this.prompt,onClickImportMenu);
	} else if (params[0]=='inline') {// show panel as INLINE tiddler content
		createImportPanel(place);
		this.$('importPanel').style.position='static';
		this.$('importPanel').style.display='block';
	} else if (config.macros.loadTiddlers)
		config.macros.loadTiddlers.handler(place,macroName,params); // any other params: loadtiddlers
}
//}}}
//{{{
// Handle link click to create/show/hide control panel
function onClickImportMenu(e) { var e=e||window.event;
	var parent=resolveTarget(e).parentNode;
	var panel=document.getElementById('importPanel');
	if (panel==undefined || panel.parentNode!=parent) panel=createImportPanel(parent);
	var isOpen=panel.style.display=='block';
	if(config.options.chkAnimate)
		anim.startAnimating(new Slider(panel,!isOpen,false,'none'));
	else
		panel.style.display=isOpen?'none':'block';
	e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); return(false);
}
//}}}
//{{{
// Create control panel: HTML, CSS
function createImportPanel(place) {
	var cmi=config.macros.importTiddlers; // abbrev
	var panel=cmi.$('importPanel');
	if (panel) { panel.parentNode.removeChild(panel); }
	setStylesheet(store.getTiddlerText('ImportTiddlersPlugin##css'),'importTiddlers');
	panel=createTiddlyElement(place,'span','importPanel',null,null)
	panel.innerHTML=store.getTiddlerText('ImportTiddlersPlugin##html');
	refreshImportList();
	if (!cmi.src.length) cmi.src=store.getTiddlerText('SiteUrl')||'';
	cmi.$('importSourceURL').value=cmi.src;
	if (!cmi.proxy.length) cmi.proxy=store.getTiddlerText('SiteProxy')||'SiteProxy';
	cmi.$('importSiteProxy').value=cmi.proxy;
	if (window.Components) { // FF3 FIXUP
		cmi.$('fileImportSource').style.display='none';
		cmi.$('importLocalPanelFix').style.display='block';
	}
	cmi.$('chkSync').checked=cmi.sync;
	cmi.$('chkImportTags').checked=cmi.importTags;
	cmi.$('chkKeepTags').checked=cmi.keepTags;
	cmi.$('chkAddTags').checked=cmi.addTags;
	cmi.$('txtNewTags').value=cmi.newTags;
	cmi.$('txtNewTags').style.display=cmi.addTags?'block':'none';
	cmi.$('chkSync').checked=cmi.sync;
	cmi.$('chkImportReport').checked=config.options.chkImportReport;
	return panel;
}
//}}}
//{{{
// process control interactions
function onClickImportButton(which,event) {
	var cmi=config.macros.importTiddlers; // abbreviation
	var list=cmi.$('importList'); if (!list) return false;
	var thePanel=cmi.$('importPanel');
	var theCollisionPanel=cmi.$('importCollisionPanel');
	var theNewTitle=cmi.$('importNewTitle');
	var count=0;
	switch (which.id)
		{
		case 'importFromFile':	// show local panel
		case 'importFromWeb':	// show HTTP panel
			cmi.local=(which.id=='importFromFile');
			cmi.showPanel('importLocalPanel',cmi.local);
			cmi.showPanel('importHTTPPanel',!cmi.local);
			break;
		case 'importOptions':	// show/hide options panel
			cmi.showPanel('importOptionsPanel',cmi.$('importOptionsPanel').style.display=='none');
			break;
		case 'fileImportSource':
		case 'importLoad':		// load import source into hidden frame
			importReport();		// if an import was in progress, generate a report
			cmi.inbound=null;	// clear the imported tiddler buffer
			refreshImportList();	// reset/resize the listbox
			if (cmi.src=='') break;
			// Load document, read it's DOM and fill the list
			cmi.loadRemoteFile(cmi.src,cmi.filterTiddlerList);
			break;
		case 'importSelectFeed':	// select a pre-defined systemServer feed URL
			var p=Popup.create(which); if (!p) return false;
			var tids=store.getTaggedTiddlers('systemServer');
			if (!tids.length)
				createTiddlyText(createTiddlyElement(p,'li'),'no pre-defined server feeds');
			for (var t=0; t<tids.length; t++) {
				var u=store.getTiddlerSlice(tids[t].title,'URL');
				var d=store.getTiddlerSlice(tids[t].title,'Description');
				if (!d||!d.length) d=store.getTiddlerSlice(tids[t].title,'description');
				if (!d||!d.length) d=u;
				createTiddlyButton(createTiddlyElement(p,'li'),tids[t].title,d,
					function(){
						var u=this.getAttribute('url');
						document.getElementById('importSourceURL').value=u;
						config.macros.importTiddlers.src=u;
						document.getElementById('importLoad').onclick();
					},
					null,null,null,{url:u});
			}
			Popup.show();
			event.cancelBubble = true;
			if (event.stopPropagation) event.stopPropagation();
			return false;
			// create popup with feed list
			// onselect, insert feed URL into input field.
			break;
		case 'importSelectAll':		// select all tiddler list items (i.e., not headings)
			importReport();		// if an import was in progress, generate a report
			for (var t=0,count=0; t < list.options.length; t++) {
				if (list.options[t].value=='') continue;
				list.options[t].selected=true;
				count++;
			}
			clearMessage(); displayMessage(cmi.countMsg.format([count]));
			cmi.$('importStart').disabled=!count;
			break;
		case 'importSelectNew':		// select tiddlers not in current document
			importReport();		// if an import was in progress, generate a report
			for (var t=0,count=0; t < list.options.length; t++) {
				list.options[t].selected=false;
				if (list.options[t].value=='') continue;
				list.options[t].selected=!store.tiddlerExists(list.options[t].value);
				count+=list.options[t].selected?1:0;
			}
			clearMessage(); displayMessage(cmi.countMsg.format([count]));
			cmi.$('importStart').disabled=!count;
			break;
		case 'importSelectChanges':		// select tiddlers that are updated from existing tiddlers
			importReport();		// if an import was in progress, generate a report
			for (var t=0,count=0; t < list.options.length; t++) {
				list.options[t].selected=false;
				if (list.options[t].value==''||!store.tiddlerExists(list.options[t].value)) continue;
				for (var i=0; i<cmi.inbound.length; i++) // find matching inbound tiddler
					{ var inbound=cmi.inbound[i]; if (inbound.title==list.options[t].value) break; }
				list.options[t].selected=(inbound.modified-store.getTiddler(list.options[t].value).modified>0); // updated tiddler
				count+=list.options[t].selected?1:0;
			}
			clearMessage(); displayMessage(cmi.countMsg.format([count]));
			cmi.$('importStart').disabled=!count;
			break;
		case 'importSelectDifferences':		// select tiddlers that are new or different from existing tiddlers
			importReport();		// if an import was in progress, generate a report
			for (var t=0,count=0; t < list.options.length; t++) {
				list.options[t].selected=false;
				if (list.options[t].value=='') continue;
				if (!store.tiddlerExists(list.options[t].value)) { list.options[t].selected=true; count++; continue; }
				for (var i=0; i<cmi.inbound.length; i++) // find matching inbound tiddler
					{ var inbound=cmi.inbound[i]; if (inbound.title==list.options[t].value) break; }
				list.options[t].selected=(inbound.modified-store.getTiddler(list.options[t].value).modified!=0); // changed tiddler
				count+=list.options[t].selected?1:0;
			}
			clearMessage(); displayMessage(cmi.countMsg.format([count]));
			cmi.$('importStart').disabled=!count;
			break;
		case 'importApplyFilter':	// filter list to include only matching tiddlers
			importReport();		// if an import was in progress, generate a report
			clearMessage();
			if (!cmi.all) // no tiddlers loaded = '0 selected'
				{ displayMessage(cmi.countMsg.format([0])); return false; }
			var hash=cmi.$('importLastFilter').value;
			cmi.inbound=cmi.filterByHash('#'+hash,cmi.all);
			refreshImportList();	// reset/resize the listbox
			break;
		case 'importStart':		// initiate the import processing
			importReport();		// if an import was in progress, generate a report
			cmi.$('importApplyToAll').checked=false;
			cmi.$('importStart').value=cmi.stopText;
			if (cmi.index>0) cmi.index=-1; // stop processing
			else cmi.index=importTiddlers(0); // or begin processing
			importStopped();
			break;
		case 'importClose':		// unload imported tiddlers or hide the import control panel
			// if imported tiddlers not loaded, close the import control panel
			if (!cmi.inbound) { thePanel.style.display='none'; break; }
			importReport();		// if an import was in progress, generate a report
			cmi.inbound=null;	// clear the imported tiddler buffer
			refreshImportList();	// reset/resize the listbox
			break;
		case 'importSkip':	// don't import the tiddler
			cmi.lastAction=which;
			var theItem	= list.options[cmi.index];
			for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==theItem.value) break;
			var theImported = cmi.inbound[j];
			theImported.status='skipped after asking';			// mark item as skipped
			theCollisionPanel.style.display='none';
			cmi.index=importTiddlers(cmi.index+1);	// resume with NEXT item
			importStopped();
			break;
		case 'importRename':		// change name of imported tiddler
			cmi.lastAction=which;
			var theItem		= list.options[cmi.index];
			for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==theItem.value) break;
			var theImported		= cmi.inbound[j];
			theImported.status	= 'renamed from '+theImported.title;	// mark item as renamed
			theImported.set(theNewTitle.value,null,null,null,null);		// change the tiddler title
			theItem.value		= theNewTitle.value;			// change the listbox item text
			theItem.text		= theNewTitle.value;			// change the listbox item text
			theCollisionPanel.style.display='none';
			cmi.index=importTiddlers(cmi.index);	// resume with THIS item
			importStopped();
			break;
		case 'importMerge':	// join existing and imported tiddler content
			cmi.lastAction=which;
			var theItem	= list.options[cmi.index];
			for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==theItem.value) break;
			var theImported	= cmi.inbound[j];
			var theExisting	= store.getTiddler(theItem.value);
			var theText	= theExisting.text+'\n----\n^^merged from: ';
			theText		+='[['+cmi.src+'#'+theItem.value+'|'+cmi.src+'#'+theItem.value+']]^^\n';
			theText		+='^^'+theImported.modified.toLocaleString()+' by '+theImported.modifier+'^^\n'+theImported.text;
			var theDate	= new Date();
			var theTags	= theExisting.getTags()+' '+theImported.getTags();
			theImported.set(null,theText,null,theDate,theTags);
			theImported.status   = 'merged with '+theExisting.title;	// mark item as merged
			theImported.status  += ' - '+theExisting.modified.formatString('MM/DD/YYYY 0hh:0mm:0ss');
			theImported.status  += ' by '+theExisting.modifier;
			theCollisionPanel.style.display='none';
			cmi.index=importTiddlers(cmi.index);	// resume with this item
			importStopped();
			break;
		case 'importReplace':		// substitute imported tiddler for existing tiddler
			cmi.lastAction=which;
			var theItem		  = list.options[cmi.index];
			for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==theItem.value) break;
			var theImported     = cmi.inbound[j];
			var theExisting	  = store.getTiddler(theItem.value);
			theImported.status  = 'replaces '+theExisting.title;		// mark item for replace
			theImported.status += ' - '+theExisting.modified.formatString('MM/DD/YYYY 0hh:0mm:0ss');
			theImported.status += ' by '+theExisting.modifier;
			theCollisionPanel.style.display='none';
			cmi.index=importTiddlers(cmi.index);	// resume with THIS item
			importStopped();
			break;
		case 'importListSmaller':		// decrease current listbox size, minimum=5
			if (list.options.length==1) break;
			list.size-=(list.size>5)?1:0;
			cmi.listsize=list.size;
			break;
		case 'importListLarger':		// increase current listbox size, maximum=number of items in list
			if (list.options.length==1) break;
			list.size+=(list.size<list.options.length)?1:0;
			cmi.listsize=list.size;
			break;
		case 'importListMaximize':	// toggle listbox size between current and maximum
			if (list.options.length==1) break;
			list.size=(list.size==list.options.length)?cmi.listsize:list.options.length;
			break;
		}
}
//}}}
//{{{
config.macros.importTiddlers.showPanel=function(place,show,skipAnim) {
	if (typeof place=='string') var place=document.getElementById(place);
	if (!place||!place.style) return;
	if(!skipAnim && anim && config.options.chkAnimate) anim.startAnimating(new Slider(place,show,false,'none'));
	else place.style.display=show?'block':'none';
}
//}}}
//{{{
function refreshImportList(selectedIndex) {
	var cmi=config.macros.importTiddlers; // abbrev
	var list=cmi.$('importList'); if (!list) return;
	// if nothing to show, reset list content and size
	if (!cmi.inbound) {
		while (list.length > 0) { list.options[0] = null; }
		list.options[0]=new Option(cmi.loadText,'',false,false);
		list.size=cmi.listsize;
		cmi.$('importLoad').disabled=false;
		cmi.$('importLoad').style.display='inline';
		cmi.$('importStart').disabled=true;
		cmi.$('importOptions').disabled=true;
		cmi.$('importOptions').style.display='none';
		cmi.$('fileImportSource').disabled=false;
		cmi.$('importFromFile').disabled=false;
		cmi.$('importFromWeb').disabled=false;
		cmi.$('importStart').value=cmi.startText;
		cmi.$('importClose').value=cmi.doneText;
		cmi.$('importSelectPanel').style.display='none';
		cmi.$('importOptionsPanel').style.display='none';
		return;
	}
	// there are inbound tiddlers loaded...
	cmi.$('importLoad').disabled=true;
	cmi.$('importLoad').style.display='none';
	cmi.$('importOptions').style.display='inline';
	cmi.$('importOptions').disabled=false;
	cmi.$('fileImportSource').disabled=true;
	cmi.$('importFromFile').disabled=true;
	cmi.$('importFromWeb').disabled=true;
	cmi.$('importClose').value=cmi.closeText;
	if (cmi.$('importSelectPanel').style.display=='none')
		cmi.showPanel('importSelectPanel',true);

	// get the sort order
	if (!selectedIndex)   selectedIndex=0;
	if (selectedIndex==0) cmi.sort='title';		// heading
	if (selectedIndex==1) cmi.sort='title';
	if (selectedIndex==2) cmi.sort='modified';
	if (selectedIndex==3) cmi.sort='tags';
	if (selectedIndex>3) {
		// display selected tiddler count
		for (var t=0,count=0; t < list.options.length; t++) {
			if (!list.options[t].selected) continue;
			if (list.options[t].value!='')
				count+=1;
			else { // if heading is selected, deselect it, and then select and count all in section
				list.options[t].selected=false;
				for ( t++; t<list.options.length && list.options[t].value!=''; t++) {
					list.options[t].selected=true;
					count++;
				}
			}
		}
		clearMessage(); displayMessage(cmi.countMsg.format([count]));
	}
	cmi.$('importStart').disabled=!count;
	if (selectedIndex>3) return; // no refresh needed

	// get the alphasorted list of tiddlers
	var tiddlers=cmi.inbound;
	tiddlers.sort(function (a,b) {if(a['title'] == b['title']) return(0); else return (a['title'] < b['title']) ? -1 : +1; });
	// clear current list contents
	while (list.length > 0) { list.options[0] = null; }
	// add heading and control items to list
	var i=0;
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	if (cmi.all.length==tiddlers.length)
		var summary=cmi.summaryMsg.format([tiddlers.length,(tiddlers.length!=1)?cmi.plural:cmi.single]);
	else
		var summary=cmi.summaryFilteredMsg.format([tiddlers.length,cmi.all.length,(cmi.all.length!=1)?cmi.plural:cmi.single]);
	list.options[i++]=new Option(summary,'',false,false);
	list.options[i++]=new Option(((cmi.sort=='title'   )?'>':indent)+' [by title]','',false,false);
	list.options[i++]=new Option(((cmi.sort=='modified')?'>':indent)+' [by date]','',false,false);
	list.options[i++]=new Option(((cmi.sort=='tags')?'>':indent)+' [by tags]','',false,false);
	// output the tiddler list
	switch(cmi.sort) {
		case 'title':
			for(var t = 0; t < tiddlers.length; t++)
				list.options[i++] = new Option(tiddlers[t].title,tiddlers[t].title,false,false);
			break;
		case 'modified':
			// sort descending for newest date first
			tiddlers.sort(function (a,b) {if(a['modified'] == b['modified']) return(0); else return (a['modified'] > b['modified']) ? -1 : +1; });
			var lastSection = '';
			for(var t = 0; t < tiddlers.length; t++) {
				var tiddler = tiddlers[t];
				var theSection = tiddler.modified.toLocaleDateString();
				if (theSection != lastSection) {
					list.options[i++] = new Option(theSection,'',false,false);
					lastSection = theSection;
				}
				list.options[i++] = new Option(indent+indent+tiddler.title,tiddler.title,false,false);
			}
			break;
		case 'tags':
			var theTitles = {}; // all tiddler titles, hash indexed by tag value
			var theTags = new Array();
			for(var t=0; t<tiddlers.length; t++) {
				var title=tiddlers[t].title;
				var tags=tiddlers[t].tags;
				if (!tags || !tags.length) {
					if (theTitles['untagged']==undefined) { theTags.push('untagged'); theTitles['untagged']=new Array(); }
					theTitles['untagged'].push(title);
				}
				else for(var s=0; s<tags.length; s++) {
					if (theTitles[tags[s]]==undefined) { theTags.push(tags[s]); theTitles[tags[s]]=new Array(); }
					theTitles[tags[s]].push(title);
				}
			}
			theTags.sort();
			for(var tagindex=0; tagindex<theTags.length; tagindex++) {
				var theTag=theTags[tagindex];
				list.options[i++]=new Option(theTag,'',false,false);
				for(var t=0; t<theTitles[theTag].length; t++)
					list.options[i++]=new Option(indent+indent+theTitles[theTag][t],theTitles[theTag][t],false,false);
			}
			break;
		}
	list.selectedIndex=selectedIndex;		  // select current control item
	if (list.size<cmi.listsize) list.size=cmi.listsize;
	if (list.size>list.options.length) list.size=list.options.length;
}
//}}}
//{{{
// re-entrant processing for handling import with interactive collision prompting
function importTiddlers(startIndex) {
	var cmi=config.macros.importTiddlers; // abbrev
	if (!cmi.inbound) return -1;
	var list=cmi.$('importList'); if (!list) return;
	var t;
	// if starting new import, reset import status flags
	if (startIndex==0)
		for (var t=0;t<cmi.inbound.length;t++)
			cmi.inbound[t].status='';
	for (var i=startIndex; i<list.options.length; i++) {
		// if list item is not selected or is a heading (i.e., has no value), skip it
		if ((!list.options[i].selected) || ((t=list.options[i].value)==''))
			continue;
		for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==t) break;
		var inbound = cmi.inbound[j];
		var theExisting = store.getTiddler(inbound.title);
		// avoid redundant import for tiddlers that are listed multiple times (when 'by tags')
		if (inbound.status=='added')
			continue;
		// don't import the 'ImportedTiddlers' history from the other document...
		if (inbound.title=='ImportedTiddlers')
			continue;
		// if tiddler exists and import not marked for replace or merge, stop importing
		if (theExisting && (inbound.status.substr(0,7)!='replace') && (inbound.status.substr(0,5)!='merge'))
			return i;
		// assemble tags (remote + existing + added)
		var newTags = '';
		if (cmi.importTags)
			newTags+=inbound.getTags()	// import remote tags
		if (cmi.keepTags && theExisting)
			newTags+=' '+theExisting.getTags(); // keep existing tags
		if (cmi.addTags && cmi.newTags.trim().length)
			newTags+=' '+cmi.newTags; // add new tags
		inbound.set(null,null,null,null,newTags.trim());
		// set the status to 'added' (if not already set by the 'ask the user' UI)
		inbound.status=(inbound.status=='')?'added':inbound.status;
		// set sync fields
		if (cmi.sync) {
			if (!inbound.fields) inbound.fields={}; // for TW2.1.x backward-compatibility
			inbound.fields['server.page.revision']=inbound.modified.convertToYYYYMMDDHHMM();
			inbound.fields['server.type']='file';
			inbound.fields['server.host']=(cmi.local&&!cmi.src.startsWith('file:')?'file:///':'')+cmi.src;
		}
		// do the import!
		store.suspendNotifications();
		store.saveTiddler(inbound.title, inbound.title, inbound.text, inbound.modifier, inbound.modified, inbound.tags, inbound.fields, true, inbound.created);
                store.fetchTiddler(inbound.title).created = inbound.created; // force creation date to imported value (needed for TW2.1.x and earlier)
		store.resumeNotifications();
		}
	return(-1);	// signals that we really finished the entire list
}
function importStopped() {
	var cmi=config.macros.importTiddlers; // abbrev
	var list=cmi.$('importList'); if (!list) return;
	var theNewTitle=cmi.$('importNewTitle');
	if (cmi.index==-1){ 
		cmi.$('importStart').value=cmi.startText;
		importReport();	// import finished... generate the report
	} else {
		// import collision...
		// show the collision panel and set the title edit field
		cmi.$('importStart').value=cmi.stopText;
		cmi.showPanel('importCollisionPanel',true);
		theNewTitle.value=list.options[cmi.index].value;
		if (cmi.$('importApplyToAll').checked && cmi.lastAction && cmi.lastAction.id!='importRename')
			onClickImportButton(cmi.lastAction);
	}
}
//}}}
//{{{
function importReport() {
	var cmi=config.macros.importTiddlers; // abbrev
	if (!cmi.inbound) return;
	// if import was not completed, the collision panel will still be open... close it now.
	var panel=cmi.$('importCollisionPanel'); if (panel) panel.style.display='none';
	// get the alphasorted list of tiddlers
	var tiddlers = cmi.inbound;
	// gather the statistics
	var count=0; var total=0;
	for (var t=0; t<tiddlers.length; t++) {
		if (!tiddlers[t].status || !tiddlers[t].status.trim().length) continue;
		if (tiddlers[t].status.substr(0,7)!='skipped') count++;
		total++;
	}
	// generate a report
	if (total) displayMessage(cmi.processedMsg.format([total]));
	if (count && config.options.chkImportReport) {
		// get/create the report tiddler
		var theReport = store.getTiddler('ImportedTiddlers');
		if (!theReport) { theReport=new Tiddler(); theReport.title='ImportedTiddlers'; theReport.text=''; }
		// format the report content
		var now = new Date();
		var newText = 'On '+now.toLocaleString()+', '+config.options.txtUserName
		newText +=' imported '+count+' tiddler'+(count==1?'':'s')+' from\n[['+cmi.src+'|'+cmi.src+']]:\n';
		if (cmi.addTags && cmi.newTags.trim().length)
			newText += 'imported tiddlers were tagged with: "'+cmi.newTags+'"\n';
		newText += '<<<\n';
		for (var t=0; t<tiddlers.length; t++) if (tiddlers[t].status)
			newText += '#[['+tiddlers[t].title+']] - '+tiddlers[t].status+'\n';
		newText += '<<<\n';
		// update the ImportedTiddlers content and show the tiddler
		theReport.text	 = newText+((theReport.text!='')?'\n----\n':'')+theReport.text;
		theReport.modifier = config.options.txtUserName;
		theReport.modified = new Date();
                store.saveTiddler(theReport.title, theReport.title, theReport.text, theReport.modifier, theReport.modified, theReport.tags, theReport.fields);
		story.displayTiddler(null,theReport.title,1,null,null,false);
		story.refreshTiddler(theReport.title,1,true);
	}
	// reset status flags
	for (var t=0; t<cmi.inbound.length; t++) cmi.inbound[t].status='';
	// mark document as dirty and let display update as needed
	if (count) { store.setDirty(true); store.notifyAll(); }
	// always show final message when tiddlers were actually loaded
	if (count) displayMessage(cmi.importedMsg.format([count,tiddlers.length,cmi.src.replace(/%20/g,' ')]));
}
//}}}
//{{{
// // File and XMLHttpRequest I/O
config.macros.importTiddlers.askForFilename=function(here) {
	var msg=here.title; // use tooltip as dialog box message
	var path=getLocalPath(document.location.href);
	var slashpos=path.lastIndexOf('/'); if (slashpos==-1) slashpos=path.lastIndexOf('\\'); 
	if (slashpos!=-1) path = path.substr(0,slashpos+1); // remove filename from path, leave the trailing slash
	var file='';
	var result='';
	if(window.Components) { // moz
		try {
			netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');

			var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
			var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
			picker.init(window, msg, nsIFilePicker.modeOpen);
			var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
			thispath.initWithPath(path);
			picker.displayDirectory=thispath;
			picker.defaultExtension='html';
			picker.defaultString=file;
			picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
			if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.path;
		}
		catch(e) { alert('error during local file access: '+e.toString()) }
	}
	else { // IE
		try { // XPSP2 IE only
			var s = new ActiveXObject('UserAccounts.CommonDialog');
			s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|';
			s.FilterIndex=3; // default to HTML files;
			s.InitialDir=path;
			s.FileName=file;
			if (s.showOpen()) var result=s.FileName;
		}
		catch(e) {  // fallback
			var result=prompt(msg,path+file);
		}
	}
	return result;
}

config.macros.importTiddlers.loadRemoteFile = function(src,callback) {
	if (src==undefined || !src.length) return null; // filename is required
	var original=src; // URL as specified
	var hashpos=src.indexOf('#'); if (hashpos!=-1) src=src.substr(0,hashpos); // URL with #... suffix removed (needed for IE)
	clearMessage();
	displayMessage(this.openMsg.format([src.replace(/%20/g,' ')]));
	if (src.substr(0,5)!='http:' && src.substr(0,5)!='file:') { // if not a URL, read from local filesystem
		var txt=loadFile(src);
		if (!txt) { // file didn't load, might be relative path.. try fixup
			var pathPrefix=document.location.href;  // get current document path and trim off filename
			var slashpos=pathPrefix.lastIndexOf('/'); if (slashpos==-1) slashpos=pathPrefix.lastIndexOf('\\'); 
			if (slashpos!=-1 && slashpos!=pathPrefix.length-1) pathPrefix=pathPrefix.substr(0,slashpos+1);
			src=pathPrefix+src;
			if (pathPrefix.substr(0,5)!='http:') src=getLocalPath(src);
			var txt=loadFile(src);
		}
		if (!txt) { // file still didn't load, report error
			displayMessage(config.macros.importTiddlers.openErrMsg.format([src.replace(/%20/g,' '),'(filesystem error)']));
		} else {
			displayMessage(config.macros.importTiddlers.readMsg.format([txt.length,src.replace(/%20/g,' ')]));
			if (version.major+version.minor*.1+version.revision*.01!=2.52) txt=convertUTF8ToUnicode(txt);
			if (callback) callback(true,original,txt,src,null);
		}
	} else {
		doHttp('GET',src,null,null,config.options.txtRemoteUsername,config.options.txtRemotePassword,callback,original,null);
	}
}

config.macros.importTiddlers.readTiddlersFromHTML=function(html){
	var remoteStore=new TiddlyWiki();
	remoteStore.importTiddlyWiki(html);
	return remoteStore.getTiddlers('title');	
}

config.macros.importTiddlers.readTiddlersFromCSV=function(CSV){
	var remoteStore=new TiddlyWiki();
	// GET NAMES
	var lines=CSV.replace(/\r/g,'').split('\n');
	var names=lines.shift().replace(/"/g,'').split(',');
	CSV=lines.join('\n');
	// ENCODE commas and newlines within quoted values
	var comma='!~comma~!'; var commaRE=new RegExp(comma,'g');
	var newline='!~newline~!'; var newlineRE=new RegExp(newline,'g');
	CSV=CSV.replace(/"([^"]*?)"/g,
		function(x){ return x.replace(/\,/g,comma).replace(/\n/g,newline); });
	// PARSE lines
	var lines=CSV.split('\n');
	for (var i=0; i<lines.length; i++) { if (!lines[i].length) continue;
		var values=lines[i].split(',');
		// DECODE commas, newlines, and doubled-quotes, and remove enclosing quotes (if any)
		for (var v=0; v<values.length; v++)
			values[v]=values[v].replace(commaRE,',').replace(newlineRE,'\n')
				.replace(/^"|"$/g,'').replace(/""/g,'"');
		// EXTRACT tiddler values
		var title=''; var text=''; var tags=[]; var fields={};
		var created=null; var when=new Date(); var who=config.options.txtUserName;
		for (var v=0; v<values.length; v++) { var val=values[v];
			if (names[v]) switch(names[v].toLowerCase()) {
				case 'title':	title=val.replace(/\[\]\|/g,'_'); break;
				case 'created': created=new Date(val); break;
				case 'modified':when=new Date(val); break;
				case 'modifier':who=val; break;
				case 'text':	text=val; break;
				case 'tags':	tags=val.readBracketedList(); break;
				default:	fields[names[v].toLowerCase()]=val; break;
			}
		}
		// CREATE tiddler in temporary store
		if (title.length)
			remoteStore.saveTiddler(title,title,text,who,when,tags,fields,true,created||when);
	}
	return remoteStore.getTiddlers('title');
}

config.macros.importTiddlers.createTiddlerFromFile=function(src,txt) {
	var t=new Tiddler();
	var pos=src.lastIndexOf("/"); if (pos==-1) pos=src.lastIndexOf("\\");
	t.title=pos==-1?src:src.substr(pos+1);
	t.text=txt; 
	t.created=t.modified=new Date();
	t.modifier=config.options.txtUserName;
	if (src.substr(src.length-3,3)=='.js') t.tags=['systemConfig'];
	return [t];
}

config.macros.importTiddlers.filterTiddlerList=function(success,params,txt,src,xhr){
	var cmi=config.macros.importTiddlers; // abbreviation
	var src=src.replace(/%20/g,' ');
	if (!success) { displayMessage(cmi.openErrMsg.format([src,xhr.status])); return; }
	cmi.all=cmi.readTiddlersFromHTML(txt);
	if (!cmi.all||!cmi.all.length) cmi.all=cmi.readTiddlersFromCSV(txt)
	if (!cmi.all||!cmi.all.length) cmi.all=cmi.createTiddlerFromFile(src,txt)
	var count=cmi.all?cmi.all.length:0;
	var querypos=src.lastIndexOf('?'); if (querypos!=-1) src=src.substr(0,querypos);
	displayMessage(cmi.foundMsg.format([count,src]));
	cmi.inbound=cmi.filterByHash(params,cmi.all); // use full URL including hash (if any)
	cmi.$('importLastFilter').value=cmi.lastFilter;
	window.refreshImportList(0);
}

config.macros.importTiddlers.filterByHash=function(src,tiddlers){
	var hashpos=src.lastIndexOf('#'); if (hashpos==-1) return tiddlers;
	var hash=src.substr(hashpos+1); if (!hash.length) return tiddlers;
	var tids=[];
	var params=hash.parseParams('anon',null,true,false,false);
	for (var p=1; p<params.length; p++) {
		switch (params[p].name) {
			case 'anon':
			case 'open':
				tids.pushUnique(params[p].value);
				break;
			case 'tag':
				if (store.getMatchingTiddlers) { // for boolean expressions - see MatchTagsPlugin
					var r=store.getMatchingTiddlers(params[p].value,null,tiddlers);
					for (var t=0; t<r.length; t++) tids.pushUnique(r[t].title);
				} else for (var t=0; t<tiddlers.length; t++)
					if (tiddlers[t].isTagged(params[p].value))
						tids.pushUnique(tiddlers[t].title);
				break;
			case 'story':
				for (var t=0; t<tiddlers.length; t++)
					if (tiddlers[t].title==params[p].value) {
						tiddlers[t].changed();
						for (var s=0; s<tiddlers[t].links.length; s++)
							tids.pushUnique(tiddlers[t].links[s]);
						break;
					}
				break;
			case 'search':
				for (var t=0; t<tiddlers.length; t++)
					if (tiddlers[t].text.indexOf(params[p].value)!=-1)
						tids.pushUnique(tiddlers[t].title);
				break;
		}
	}
	var matches=[];
	for (var t=0; t<tiddlers.length; t++)
		if (tids.contains(tiddlers[t].title))
			matches.push(tiddlers[t]);
	displayMessage(config.macros.importTiddlers.filterMsg.format([matches.length,hash]));
	config.macros.importTiddlers.lastFilter=hash;
	return matches;
}
//}}}
/***
!!!Control panel CSS
//{{{
!css
#importPanel {
	display: none; position:absolute; z-index:11; width:35em; right:105%; top:3em;
	background-color: #eee; color:#000; font-size: 8pt; line-height:110%;
	border:1px solid black; border-bottom-width: 3px; border-right-width: 3px;
	padding: 0.5em; margin:0em; -moz-border-radius:1em;-webkit-border-radius:1em;
}
#importPanel a, #importPanel td a { color:#009; display:inline; margin:0px; padding:1px; }
#importPanel table { width:100%; border:0px; padding:0px; margin:0px; font-size:8pt; line-height:110%; background:transparent; }
#importPanel tr { border:0px;padding:0px;margin:0px; background:transparent; }
#importPanel td { color:#000; border:0px;padding:0px;margin:0px; background:transparent; }
#importPanel select { width:100%;margin:0px;font-size:8pt;line-height:110%;}
#importPanel input  { width:98%;padding:0px;margin:0px;font-size:8pt;line-height:110%}
#importPanel .box { border:1px solid #000; background-color:#eee; padding:3px 5px; margin-bottom:5px; -moz-border-radius:5px;-webkit-border-radius:5px;}
#importPanel .topline { border-top:1px solid #999; padding-top:2px; margin-top:2px; }
#importPanel .rad { width:auto; }
#importPanel .chk { width:auto; margin:1px;border:0; }
#importPanel .btn { width:auto; }
#importPanel .btn1 { width:98%; }
#importPanel .btn2 { width:48%; }
#importPanel .btn3 { width:32%; }
#importPanel .btn4 { width:23%; }
#importPanel .btn5 { width:19%; }
#importPanel .importButton { padding: 0em; margin: 0px; font-size:8pt; }
#importPanel .importListButton { padding:0em 0.25em 0em 0.25em; color: #000000; display:inline }
#backstagePanel #importPanel { left:10%; right:auto; }
!end
//}}}
!!!Control panel HTML
//{{{
!html
<!-- source and report -->
<table><tr><td align=left>
	import from
	<input type="radio" class="rad" name="importFrom" id="importFromFile" value="file" CHECKED
		onclick="onClickImportButton(this,event)" title="show file controls"> local file
	<input type="radio" class="rad" name="importFrom" id="importFromWeb"  value="http"
		onclick="onClickImportButton(this,event)" title="show web controls"> web server
</td><td align=right>
	<input type=checkbox class="chk" id="chkImportReport"
		onClick="config.options['chkImportReport']=this.checked;"> create report
</td></tr></table>

<div class="box" id="importSourcePanel" style="margin:.5em">
<div id="importLocalPanel" style="display:block;margin-bottom:2px;"><!-- import from local file  -->
enter or browse for source path/filename<br>
<input type="file" id="fileImportSource" size=57 style="width:100%"
	onKeyUp="config.macros.importTiddlers.src=this.value"
	onChange="config.macros.importTiddlers.src=this.value;document.getElementById('importLoad').onclick()">
<div id="importLocalPanelFix" style="display:none"><!-- FF3 FIXUP -->
	<input type="text" id="fileImportSourceFix" style="width:90%"
		title="Enter a path/file to import"
		onKeyUp="config.macros.importTiddlers.src=this.value"
		onChange="config.macros.importTiddlers.src=this.value;document.getElementById('importLoad').onclick()">
	<input type="button" id="fileImportSourceFixButton" style="width:7%" value="..."
		title="Select a path/file to import"
		onClick="var r=config.macros.importTiddlers.askForFilename(this); if (!r||!r.length) return;
			document.getElementById('fileImportSourceFix').value=r;
			config.macros.importTiddlers.src=r;
			document.getElementById('importLoad').onclick()">
</div><!--end FF3 FIXUP-->
</div><!--end local-->
<div id="importHTTPPanel" style="display:none;margin-bottom:2px;"><!-- import from http server -->
<table><tr><td align=left>
	enter a URL or <a href="javascript:;" id="importSelectFeed"
		onclick="return onClickImportButton(this,event)" title="select a pre-defined 'systemServer' URL">
		select a server</a><br>
</td><td align=right>
	<input type="checkbox" class="chk" id="importUsePassword"
		onClick="config.macros.importTiddlers.usePassword=this.checked;
			config.macros.importTiddlers.showPanel('importIDPWPanel',this.checked,true);">password
	<input type="checkbox" class="chk" id="importUseProxy"
		onClick="config.macros.importTiddlers.useProxy=this.checked;
			config.macros.importTiddlers.showPanel('importSiteProxy',this.checked,true);">proxy
</td></tr></table>
<input type="text" id="importSiteProxy" style="display:none;margin-bottom:1px" onfocus="this.select()" value="SiteProxy"
	onKeyUp="config.macros.importTiddlers.proxy=this.value"
	onChange="config.macros.importTiddlers.proxy=this.value;">
<input type="text" id="importSourceURL" onfocus="this.select()" value="SiteUrl"
	onKeyUp="config.macros.importTiddlers.src=this.value"
	onChange="config.macros.importTiddlers.src=this.value;">
<div id="importIDPWPanel" style="text-align:center;margin-top:2px;display:none";>
username: <input type=text id="txtImportID" style="width:25%" 
	onChange="config.options.txtRemoteUsername=this.value;">
 password: <input type=password id="txtImportPW" style="width:25%" 
	onChange="config.options.txtRemotePassword=this.value;">
</div><!--end idpw-->
</div><!--end http-->
</div><!--end source-->

<div class="box" id="importSelectPanel" style="display:none;margin:.5em;">
<table><tr><td align=left>
select:
<a href="javascript:;" id="importSelectAll"
	onclick="return onClickImportButton(this)" title="SELECT all tiddlers">
	all</a>
&nbsp;<a href="javascript:;" id="importSelectNew"
	onclick="return onClickImportButton(this)" title="SELECT tiddlers not already in destination document">
	added</a>
&nbsp;<a href="javascript:;" id="importSelectChanges"
	onclick="return onClickImportButton(this)" title="SELECT tiddlers that have been updated in source document">
	changes</a>
&nbsp;<a href="javascript:;" id="importSelectDifferences"
	onclick="return onClickImportButton(this)" title="SELECT tiddlers that have been added or are different from existing tiddlers">
	differences</a>
</td><td align=right>
<a href="javascript:;" id="importListSmaller"
	onclick="return onClickImportButton(this)" title="SHRINK list size">
	&nbsp;&#150;&nbsp;</a>
<a href="javascript:;" id="importListLarger"
	onclick="return onClickImportButton(this)" title="GROW list size">
	&nbsp;+&nbsp;</a>
<a href="javascript:;" id="importListMaximize"
	onclick="return onClickImportButton(this)" title="MAXIMIZE/RESTORE list size">
	&nbsp;=&nbsp;</a>
</td></tr></table>
<select id="importList" size=8 multiple
	onchange="setTimeout('refreshImportList('+this.selectedIndex+')',1)">
	<!-- NOTE: delay refresh so list is updated AFTER onchange event is handled -->
</select>
<div style="text-align:center">
	<a href="javascript:;"
		title="click for help using filters..."
		onclick="alert('A filter consists of one or more space-separated combinations of: tiddlertitle, tag:[[tagvalue]], tag:[[tag expression]] (requires MatchTagsPlugin), story:[[TiddlerName]], and/or search:[[searchtext]]. Use a blank filter to restore the list of all tiddlers.'); return false;"
	>filter</a>
	<input type="text" id="importLastFilter" style="margin-bottom:1px; width:65%"
		title="Enter a combination of one or more filters. Use a blank filter for all tiddlers."
		onfocus="this.select()" value=""
		onKeyUp="config.macros.importTiddlers.lastFilter=this.value"
		onChange="config.macros.importTiddlers.lastFilter=this.value;">
	<input type="button" id="importApplyFilter" style="width:20%" value="apply"
		title="filter list of tiddlers to include only those that match certain criteria"
		onclick="return onClickImportButton(this)">
	</div>
</div><!--end select-->

<div class="box" id="importOptionsPanel" style="text-align:center;margin:.5em;display:none;">
	apply tags: <input type=checkbox class="chk" id="chkImportTags" checked
		onClick="config.macros.importTiddlers.importTags=this.checked;">from source&nbsp;
	<input type=checkbox class="chk" id="chkKeepTags" checked
		onClick="config.macros.importTiddlers.keepTags=this.checked;">keep existing&nbsp;
	<input type=checkbox class="chk" id="chkAddTags" 
		onClick="config.macros.importTiddlers.addTags=this.checked;
			config.macros.importTiddlers.showPanel('txtNewTags',this.checked,false);
			if (this.checked) document.getElementById('txtNewTags').focus();">add tags<br>
	<input type=text id="txtNewTags" style="margin-top:4px;display:none;" size=15 onfocus="this.select()" 
		title="enter tags to be added to imported tiddlers" 
		onKeyUp="config.macros.importTiddlers.newTags=this.value;
		document.getElementById('chkAddTags').checked=this.value.length>0;" autocomplete=off>
	<nobr><input type=checkbox class="chk" id="chkSync" 
		onClick="config.macros.importTiddlers.sync=this.checked;">
		link tiddlers to source document (for sync later)</nobr>
</div><!--end options-->

<div id="importButtonPanel" style="text-align:center">
	<input type=button id="importLoad"	class="importButton btn3" value="open"
		title="load listbox with tiddlers from source document"
		onclick="onClickImportButton(this)">
	<input type=button id="importOptions"	class="importButton btn3" value="options..."
		title="set options for tags, sync, etc."
		onclick="onClickImportButton(this)">
	<input type=button id="importStart"	class="importButton btn3" value="import"
		title="start/stop import of selected source tiddlers into current document"
		onclick="onClickImportButton(this)">
	<input type=button id="importClose"	class="importButton btn3" value="done"
		title="clear listbox or hide control panel"
		onclick="onClickImportButton(this)">
</div>

<div class="none" id="importCollisionPanel" style="display:none;margin:.5em 0 .5em .5em;">
	<table><tr><td style="width:65%" align="left">
		<table><tr><td align=left>
			tiddler already exists:
		</td><td align=right>
			<input type=checkbox class="chk" id="importApplyToAll" 
			onclick="document.getElementById('importRename').disabled=this.checked;"
			checked>apply to all
		</td></tr></table>
		<input type=text id="importNewTitle" size=15 autocomplete=off">
	</td><td style="width:34%" align="center">
		<input type=button id="importMerge"
			class="importButton" style="width:47%" value="merge"
			title="append the incoming tiddler to the existing tiddler"
			onclick="onClickImportButton(this)"><!--
		--><input type=button id="importSkip"
			class="importButton" style="width:47%" value="skip"
			title="do not import this tiddler"
			onclick="onClickImportButton(this)"><!--
		--><br><input type=button id="importRename"
			class="importButton" style="width:47%" value="rename"
			title="rename the incoming tiddler"
			onclick="onClickImportButton(this)"><!--
		--><input type=button id="importReplace"
			class="importButton" style="width:47%" value="replace"
			title="discard the existing tiddler"
			onclick="onClickImportButton(this)">
	</td></tr></table>
</div><!--end collision-->
!end
//}}}
***/
 
/***
|Name|ImportTiddlersPluginInfo|
|Source|http://www.TiddlyTools.com/#ImportTiddlersPlugin|
|Documentation|http://www.TiddlyTools.com/#ImportTiddlersPluginInfo|
|Version|4.6.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|documentation|
|Description|documentation for ImportTiddlersPlugin|
Combine tiddlers from any two TiddlyWiki documents.  An interactive control panel lets you pick a source document and import selected tiddlers, with prompting for skip, rename, merge or replace actions when importing tiddlers that match existing titles.  Generates a detailed report of import 'history' in ImportedTiddlers.
!!!!!Usage
<<<
{{{<<importTiddlers>>}}} or {{{<<importTiddlers core>>}}}
invokes the built-in importTiddlers macro (TW2.1.x+).  If installed in documents using TW2.0.x or earlier, fallback is to use 'link' display (see below)

{{{<<importTiddlers link label tooltip>>}}}
The ''link'' keyword creates an "import tiddlers" link that when clicked to show/hide import control panel.  ''label'' and ''tooltip'' are optional text parameters (enclosed in quotes or {{{[[...]]}}}, and allow you to override the default display text for the link and the mouseover help text, respectively.

{{{<<importTiddlers inline>>}}}
creates import control panel directly in tiddler content

<<importTiddlers inline>>

Enter a document URL or press "..." to select a TiddlyWiki file to import, and then press ''[open]''.  //Note: There may be a delay before the list of tiddlers appears.//  Use the ''[-]'', ''[+]'', or ''[=]'' links to adjust the listbox size so you can view more (or less) tiddler titles at one time.

Select one or more titles from the listbox.  Use CTRL-click or SHIFT-click to select/deselect individual titles.  Click on ''all'', ''new'', ''changes'', or ''differences'' to automatically select a subset of tiddlers from the list, based on a comparison of the two documents:
*''all'' selects ALL tiddlers from the import source document, even if they have not been changed.
*''new'' selects only tiddlers that are found in the import source document, but do not yet exist in the destination document
*''changes'' selects only tiddlers that exist in both documents but that are newer in the source document
*''differences'' selects all new and existing tiddlers that are different from the destination document (even if destination tiddler is newer)

Press ''[import]'' to begin copying tiddlers to the current document.  If an 'inbound' tiddler matches one that already exists in the document, the import process pauses and the tiddler title is displayed in an input field, along with four push buttons: ''skip'', ''rename'', ''merge'' and ''replace''.
* to bypass importing the tiddler, press ''skip''
* to give the inbound tiddler a different name, so that both the old and new tiddlers will exist when the import is done, enter a new title in the input field and press ''rename'' 
* to combine the content from both tiddlers into a single tiddler so you can then edit it later to eliminate unwanted content, press ''merge''
* to overwrite the existing tiddler with the imported one (discarding the previous content), press ''[replace]''

''Import Report History''

Whenever tiddlers are imported, a report is generated into a tiddler named [[ImportedTiddlers]], recording when the latest import was performed, the number of tiddlers successfully imported, from what location, and by whom, as well as a list of the tiddlers that were processed.  When more tiddlers are imported at a later time, a new report is //added// to the existing [[ImportedTiddlers]], above the previous report (i.e., at the top of the tiddler), so that a history of imports is maintained.  If this record is not desired, you can delete [[ImportedTiddlers]] at any time.

Note: You can prevent a report from being generated for any given import activity by clearing the "create a report" checkbox before pressing the ''import'' button
<<<
!!!!!Installation Notes
<<<
* As of 6/27/2007, support for TW2.1.x and earlier have been moved to [[ImportTiddlersPluginPatch]].  ''//Only install the patch plugin when using TW2.1.x or earlier.//''
<<<
!!!!!Revisions
<<<
2009.10.06 4.6.0 added createTiddlerFromFile (import text files)
2009.09.27 4.5.5 in readTiddlersFromCSV(), strip \r from input and fixed handling for quoted values
2009.09.12 4.5.4 fixed 'return false' to prevent IE page transition. Also, moved html/css definitions to separate sections
2009.08.23 4.5.3 in importTiddlers(), add 'file:///' to local server.host sync field only if not already present in URL
2009.08.20 4.5.2 only use SiteURL/SiteProxy values if control panel value has not yet been set
2009.07.03 4.5.1 fixups for TW252: doHttp() doesn't return XHR and convertUTF8ToUnicode() not needed for local I/O
2009.05.04 4.5.0 import from CSV-formatted files
2009.03.04 4.4.2 in createImportPanel(), init option checkboxes so display matches internal state variables
2009.02.26 4.4.1 use macro-specific definition of $() function abbreviation (avoids conflict with JQuery)
2008.09.30 4.4.0 added fallback definition of merge() for use with TW2.0.x and TW1.2.x
2008.08.12 4.3.3 rewrite backstage and shadow tiddler definitions for easier customization
2008.08.05 4.3.2 rewrote loadRemoteFile() to eliminate use of platform-specific fileExists() function
2008.06.29 4.3.1 More layout/animation work for simpler sequential interaction.  Code reduction/cleanup
2008.06.28 4.3.0 HTML and CSS cleanup and tweaks to layout.  Added animation to panels
2008.06.22 4.2.0 For FireFox, use HTML with separate text+button control instead of type='file' control
2008.06.05 4.1.0 in filterByHash(), added support for boolean tag expressions using getMatchingTiddlers() (defined by MatchTagsPlugin)
2008.05.12 4.0.2 automatically tweak the backstage "import" task to add the ImportTiddlers control panel
2008.04.30 4.0.1 trim #... suffix for loading files/URLs in IE
2008.04.30 4.0.0 added source filtering (using URL paramifiers).  Also, abbreviations for code-size reduction.
2008.04.13 3.9.0 added 'apply to all' checkbox for collision processing
2008.03.26 3.8.0 added support for selecting pre-defined systemServer URLs
2008.03.25 3.7.0 added support for setting 'server' fields on imported tiddlers (for later synchronizing of changes)
2008.01.03 3.6.0 in loadRemoteFile(), use lower-level doHttp() instead of loadRemoteFile() in order to support username/password access to remote server
2007.10.30 3.5.6 update [[ImportTiddlers]] shadow tiddler definition to include "inline" link
2007.06.27 3.5.5 added missing 'fields' params to saveTiddler() calls.  Fixes problem where importing tiddlers would lose the custom fields.  Also, moved functions for TW2.1.x to [[ImportTiddlersPluginPatch2.1.x]].
2007.06.25 3.5.4 added calls to store.suspendNotifications() and store.resumeNotifications().  Eliminates redisplay processing overhead DURING import activities
2007.04.29 3.5.3 in refreshImportList() when inbound tiddlers are loaded, change "close" button to "done", and disable certain controls to creates a modal condition, so that actions that reload tiddlers cannot be performed unless "done" is first pressed to end the mode..
2007.04.28 3.5.2 in handler(), added param support for custom link label/prompt
2007.04.19 3.5.1 in readTiddlersFromHTML(), for TW2.2 and above, use importTiddlyWiki() (new core functionality) to get tiddlers from remote file content.  Also, copied updated TW21Loader.prototype.internalizeTiddler() definition from TW2.2b5 so plugin can read tiddlers from TW2.2+ even when running under TW2.1.x
2007.03.22 3.5.0 in refreshImportList(), add handling for 'select section' when a heading is selected.  Makes it really easy to import by tag or date!
2007.03.21 3.4.0 split loadTiddlers functionality into separate plugin (see [[LoadTiddlersPlugin]])
2007.03.20 3.3.1 tweak to previous change to allow relative file references via http: (bypasses getLocalPath() so remote URL will be used)
2007.03.20 3.3.0 added support for local, relative file references: in loadRemoteFile(), check for fileExists().  If not found, prepend relative path and retry.
2007.02.24 3.2.1 re-labeled control panel "open" button to "load"
2007.02.09 3.2.0 loadTiddlers: added support for "noReload" tag (prevents overwriting existing tiddler, even if inbound tiddler is newer)
2007.02.08 3.1.3 loadTiddlers: added missing code and documentation for "newTags" handling (a feature change from long, long ago that somehow got lost!)
2006.11.14 3.1.2 fix macro handler parameter declaration (double-pasted param list corrupts IE)
2006.11.13 3.1.1 use apply() method to invoke hijacked core handler
2006.11.13 3.1.0 hijack built-in importTiddlers.handler() to co-exist with plugin interface.  If no params or 'core' keyword, display core interface.  "link" param embeds "import tiddlers" link that shows floating panel when clicked.
2006.10.12 3.0.8 in readTiddlersFromHTML(), fallback to find end of store area by matching "/body" when POST-BODY-START is not present (backward compatibility for older documents)
2006.09.10 3.0.7 in readTiddlersFromHTML(), find end of store area by matching "POST-BODY-START" instead of "/body" 
2006.08.16 3.0.6 Use higher-level store.saveTiddler() instead of store.addTiddler() to avoid conflicts with adaptations that hijack low-level tiddler handling.  in CreateImportPanel(), removed "refresh listbox after every tiddler change".
2006.07.29 3.0.5 added noChangeMsg to loadTiddlers processing.  if not 'quiet' mode, reports skipped tiddlers.
2006.04.18 3.0.4 in loadTiddlers.handler, fixed parsing of "prompt:" param. Also, corrected parameters mismatch in loadTiddlers() callback function definition (order of params was wrong, resulting in filters NOT being applied)
2006.04.12 3.0.3 moved many display messages to macro properties for easier L10N translations via 'lingo' definitions.
2006.04.12 3.0.2 more work on 'core candidate' code.  Proposed API now defines "loadRemoteFile()" for XMLHttpRequest processing with built in fallback for handling local filesystem access, and readTiddlersFromHTML() to process the resulting source HTML content.
2006.04.04 3.0.1 in refreshImportList(), when using [by tags], tiddlers without tags are now included in a new "untagged" psuedo-tag list section
2006.04.04 3.0.0 Separate non-interactive {{{<<importTiddlers...>>}}} macro functionality for incorporation into TW2.1 core and renamed as {{{<<loadTiddlers>>}}} macro.  New parameters for loadTiddlers: ''label:text'' and ''prompt:text'' for link creation,  ''ask'' for filename/URL, ''tag:text'' for filtering, "confirm" for accept/reject of individual inbound tiddlers.  Removed support for "importReplace/importPublic" tags and "force" param (unused feature). 
2006.03.30 2.9.1 when extracting store area from remote URL, look for "</body>" instead of "</body>\n</html>" so it will match even if the "\n" is absent from the source.
2006.03.30 2.9.0 added optional 'force' macro param.  When present, autoImportTiddlers() bypasses the checks for importPublic and importReplace.  Based on a request from Tom Otvos.
2006.03.28 2.8.1 in loadImportFile(), added checks to see if 'netscape' and 'x.overrideMimeType()' are defined (not in IE). Also, when extracting store area, look for "</body>\n</html>" and omit extra content that may have been added to the end of the file.
2006.02.21 2.8.0 added support for "tiddler:TiddlerName" filtering parameter in auto-import processing
2006.02.21 2.7.1 Clean up layout problems with IE.  (Use tables for alignment instead of SPANs styled with float:left and float:right)
2006.02.21 2.7.0 Added "local file" and "web server" radio buttons.  Default remote URL uses value from [[SiteURL]].  Also, added 'proxy' option, using value from [[SiteProxy]] as prefix to permit cross-domain document access via server-side scripting.
2006.02.17 2.6.0 Removed "differences only" listbox display mode, replaced with selection filter 'presets': all/new/changes/differences.  fixed init of "add new tags" checkbox
2006.02.16 2.5.4 added checkbox options to control "import remote tags" and "keep existing tags" behavior, in addition to existing "add new tags" functionality.
2006.02.14 2.5.3 FF1501 corrected unintended global 't' (loop index) in importReport() and autoImportTiddlers()
2006.02.10 2.5.2 corrected unintended global variable in importReport().
2006.02.05 2.5.1 moved globals from window.* to config.macros.importTiddlers.* to avoid FireFox 1.5.0.1 crash bug when referencing globals
2006.01.18 2.5.0 added checkbox for "create a report".  Default is to create/update the ImportedTiddlers report.
2006.01.15 2.4.1 added "importPublic" tag and inverted default so that auto sharing is NOT done unless tagged with importPublic
2006.01.15 2.4.0 Added support for tagging tiddlers with importSkip, importReplace, and/or importPrivate to enable/disable overwriting or sharing with others when using auto-import macro syntax.  Defaults: don't overwrite existing tiddlers, and allow your tiddlers to be auto-imported by others.
2006.01.15 2.3.2 Added "ask" parameter to confirm each tiddler before importing (for use with auto-importing)
2006.01.15 2.3.1 Strip TW core scripts from import source content and load just the storeArea into the hidden IFRAME to prevent imported document's core code from being invoked.  Also, when importing local documents, use convertUTF8ToUnicode() to support international characters sets.
2006.01.12 2.3.0 Reorganized code to use callback function for loading import files to support event-driven I/O via an ASYNCHRONOUS XMLHttpRequest instead of waiting for remote hosts to respond to URL requests.  Added non-interactive 'batch' mode, using macro parameters to specify source path/file or URL, and select tiddlers to import.  Improved messages and added optional 'quiet' switch for batch mode to eliminate //most// feedback.
2006.01.11 2.2.0 Added "[by tags]" to list of tiddlers, based on code submitted by BradleyMeck
2006.01.08 2.1.0 IMPORT FROM ANYWHERE!!! re-write getImportedTiddlers() logic to either read a local file (using local I/O), OR... read a remote file, using a combination of XML and an iframe to permit cross-domain reading of DOM elements.  Adapted from example code and techniques courtesy of Jonny LeRoy.
2006.01.06 2.0.2 When refreshing list contents, fixed check for tiddlerExists() when "show differences only" is selected, so that imported tiddlers that don't exist in the current file will be recognized as differences and included in the list.
2006.01.04 2.0.1 When "show differences only" is NOT checked, import all tiddlers that have been selected even when they have a matching title and date.
2005.12.27 2.0.0 Update for TW2.0
Defer initial panel creation and only register a notification function when panel first is created
2005.12.22 1.3.1 tweak formatting in importReport() and add 'discard report' link to output
2005.12.03 1.3.0 Dynamically create/remove importPanel as needed to ensure only one instance of interface elements exists, even if there are multiple instances of macro embedding.  Also, dynamically create/recreate importFrame each time an external TW document is loaded for importation (reduces DOM overhead and ensures a 'fresh' frame for each document)
2005.11.29 1.2.1 fixed formatting of 'detail info' in importReport()
2005.11.11 1.2.0 added 'inline' param to embed controls in a tiddler
2005.11.09 1.1.0 only load HTML and CSS the first time the macro handler is called.  Allows for redundant placement of the macro without creating multiple instances of controls with the same ID's.
2005.10.25 1.0.5 fixed typo in importReport() that prevented reports from being generated
2005.10.09 1.0.4 combined documentation with plugin code instead of using separate tiddlers
2005.08.05 1.0.3 moved CSS and HTML definitions into plugin code instead of using separate tiddlers
2005.07.27 1.0.2 core update 1.2.29: custom overlayStyleSheet() replaced with new core setStylesheet()
2005.07.23 1.0.1 added parameter checks and corrected addNotification() usage
2005.07.20 1.0.0 Initial Release
<<<
/***
|Name|ImportTiddlersPluginPatch|
|Source|http://www.TiddlyTools.com/#ImportTiddlersPluginPatch|
|Version|4.4.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Requires|ImportTiddlersPlugin|
|Description|backward-compatible function patches for use with ImportTiddlersPlugin and TW2.1.x or earlier|
!!!!!Usage
<<<
The current version ImportTiddlersPlugin is compatible with the TW2.2.x core functions.  This "patch" plugin provides additional functions needed to enable the current version of ImportTiddlersPlugin to operate correctly under TW2.1.x or earlier.

{{medium{You do not need to install this plugin if you are using TW2.2.0 or above}}}
(though it won't hurt anything if you do... it will just take up more space).
<<<
!!!!!Revisions
<<<
2008.09.30 [4.4.0] added safety check for TW21Loader object and forward-compatible loadFromDiv() prototype to permit use with TW2.0.x and TW1.2.x.
2008.08.05 [4.3.2] rewrote loadRemoteFile to eliminate use of platform-specific fileExists() function
2008.01.03 [3.6.0] added support for passing txtRemoteUsername and txtRemotePassword for accessing password-protected remote servers
2007.06.27 [3.5.5] compatibility functions split from ImportTiddlersPlugin
|please see [[ImportTiddlersPlugin]] for additional revision details|
2005.07.20 [1.0.0] Initial Release
<<<
!!!!!Code
***/
//{{{
// these functions are only defined when installed in TW2.1.x and earlier... 
if (version.major+version.minor/10 <= 2.1) {

// Version
version.extensions.ImportTiddlersPluginPatch= {major: 4, minor: 4, revision: 0, date: new Date(2008,9,30)};

// fixups for TW2.0.x and earlier
if (window.merge==undefined) window.merge=function(dst,src,preserveExisting)
	{ for (p in src) if (!preserveExisting||dst[p]===undefined) dst[p]=src[p]; return dst; }
if (config.macros.importTiddlers==undefined) config.macros.importTiddlers={ };

config.macros.importTiddlers.loadRemoteFile = function(src,callback,quiet) {
	if (src==undefined || !src.length) return null; // filename is required
	if (!quiet) clearMessage();
	if (!quiet) displayMessage(this.openMsg.format([src]));

	if (src.substr(0,5)!="http:" && src.substr(0,5)!="file:") { // if not a URL, read from local filesystem
		var txt=loadFile(src);
		if (!txt) { // file didn't load, might be relative path.. try fixup
			var pathPrefix=document.location.href;  // get current document path and trim off filename
			var slashpos=pathPrefix.lastIndexOf("/"); if (slashpos==-1) slashpos=pathPrefix.lastIndexOf("\\"); 
			if (slashpos!=-1 && slashpos!=pathPrefix.length-1) pathPrefix=pathPrefix.substr(0,slashpos+1);
			src=pathPrefix+src;
			if (pathPrefix.substr(0,5)!="http:") src=getLocalPath(src);
			var txt=loadFile(src);
		}
		if (!txt) { // file still didn't load, report error
			if (!quiet) displayMessage(config.macros.importTiddlers.openErrMsg.format([src.replace(/%20/g," "),"(filesystem error)"]));
		} else {
			if (!quiet) displayMessage(config.macros.importTiddlers.readMsg.format([txt.length,src.replace(/%20/g," ")]));
			if (callback) callback(true,src,convertUTF8ToUnicode(txt),src,null);
		}
	} else {
		var x; // get an request object
		try {x = new XMLHttpRequest()} // moz
		catch(e) {
			try {x = new ActiveXObject("Msxml2.XMLHTTP")} // IE 6
			catch (e) {
				try {x = new ActiveXObject("Microsoft.XMLHTTP")} // IE 5
				catch (e) { return }
			}
		}
		// setup callback function to handle server response(s)
		x.onreadystatechange = function() {
			if (x.readyState == 4) {
				if (x.status==0 || x.status == 200) {
					if (!quiet) displayMessage(config.macros.importTiddlers.readMsg.format([x.responseText.length,src]));
					if (callback) callback(true,src,x.responseText,src,x);
				}
				else {
					if (!quiet) displayMessage(config.macros.importTiddlers.openErrMsg.format([src,x.status]));
				}
			}
		}
		// get privileges to read another document's DOM via http:// or file:// (moz-only)
		if (typeof(netscape)!="undefined") {
			try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); }
			catch (e) { if (!quiet) displayMessage(e.description?e.description:e.toString()); }
		}
		// send the HTTP request
		try {
			var url=src+(src.indexOf('?')<0?'?':'&')+'nocache='+Math.random();
			x.open("GET",src,true,config.options.txtRemoteUsername,config.options.txtRemotePassword);
			if (x.overrideMimeType) x.overrideMimeType('text/html');
			x.send(null);
		}
		catch (e) {
			if (!quiet) {
				displayMessage(config.macros.importTiddlers.openErrMsg.format([src,"(unknown)"]));
				displayMessage(e.description?e.description:e.toString());
			}
		}
	}
}

config.macros.importTiddlers.readTiddlersFromHTML=function(html) {
	// for TW2.1 and earlier
	// extract store area from html 
	var start=html.indexOf('<div id="storeArea">');
	var end=html.indexOf("<!--POST-BODY-START--"+">",start);
	if (end==-1) var end=html.indexOf("</body"+">",start); // backward-compatibility for older documents
	var sa="<html><body>"+html.substring(start,end)+"</body></html>";

	// load html into iframe document
	var f=document.getElementById("loaderFrame"); if (f) document.body.removeChild(f);
	f=document.createElement("iframe"); f.id="loaderFrame";
	f.style.width="0px"; f.style.height="0px"; f.style.border="0px";
	document.body.appendChild(f);
	var d=f.document;
	if (f.contentDocument) d=f.contentDocument; // For NS6
	else if (f.contentWindow) d=f.contentWindow.document; // For IE5.5 and IE6
	d.open(); d.writeln(sa); d.close();

	// read tiddler DIVs from storeArea DOM element	
	var sa = d.getElementById("storeArea");
	if (!sa) return null;
	sa.normalize();
	var nodes = sa.childNodes;
	if (!nodes || !nodes.length) return null;
	var tiddlers = [];
	for(var t = 0; t < nodes.length; t++) {
		var title = null;
		if(nodes[t].getAttribute)
			title = nodes[t].getAttribute("title"); // TW 2.2+
		if(!title && nodes[t].getAttribute)
			title = nodes[t].getAttribute("tiddler"); // TW 2.1.x
		if(!title && nodes[t].id && (nodes[t].id.substr(0,5) == "store"))
			title = nodes[t].id.substr(5); // TW 1.2.x
		if(title && title != "")
			tiddlers.push((new Tiddler()).loadFromDiv(nodes[t],title));
	}
	return tiddlers;
}

// // FORWARD-COMPATIBLE SUPPORT FOR TW2.1.x
// // enables reading tiddler definitions using TW2.2+ storeArea format, even when plugin is running under TW2.1.x
if (typeof TW21Loader!="undefined") {
TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node) {
	var e = node.firstChild;
	var text = null;
	if(node.getAttribute("tiddler"))
		text = getNodeText(e).unescapeLineBreaks();
	else {
		while(e.nodeName!="PRE" && e.nodeName!="pre") e = e.nextSibling;
		text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
	}
	var modifier = node.getAttribute("modifier");
	var c = node.getAttribute("created");
	var m = node.getAttribute("modified");
	var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
	var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
	var tags = node.getAttribute("tags");
	var fields = {};
	var attrs = node.attributes;
	for(var i = attrs.length-1; i >= 0; i--) {
		var name = attrs[i].name;
		if (attrs[i].specified && !TiddlyWiki.isStandardField(name))
			fields[name] = attrs[i].value.unescapeLineBreaks();
		
	}
	tiddler.assign(title,text,modifier,modified,tags,created,fields);
	return tiddler;
};
}

// FORWARD-COMPATIBLE SUPPORT FOR TW2.0.x and TW1.2.x
// enables reading tiddler definitions using TW2.2+ storeArea format, even when plugin is running under TW2.0.x or TW1.2.x
if (typeof Tiddler.prototype.loadFromDiv!="undefined") {
Tiddler.prototype.loadFromDiv = function(node,title) { // Load a tiddler from an HTML DIV
	var e = node.firstChild;
	var text = null;
	if(node.getAttribute("tiddler")) {
		// get merged text from adjacent text nodes
		var t=""; while(e&&e.nodeName=="#text") { t+=e.nodeValue; e=e.nextSibling; }
		text = Tiddler.unescapeLineBreaks(t);
	} else {
		while(e.nodeName!="PRE" && e.nodeName!="pre") e = e.nextSibling;
		text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
	}
	var modifier = node.getAttribute("modifier");
	var c = node.getAttribute("created");
	var m = node.getAttribute("modified");
	var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
	var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
	var tags = node.getAttribute("tags");
	this.set(title,text,modifier,modified,tags,created);
	return this;
}
}

} // END OF pre-TW2.2 backward-compatibility functions
//}}}
var ImportWizard, WizardMaker;

(function($) {
window.WizardMaker = function(place, wizard) {
	var steps = wizard[0];
	var options = wizard[1] || {};
	$("<h1 />").text(options.heading || "Wizard").appendTo(place);
	var wizard = this;
	$('<button class="button">restart wizard</button>').click(function(ev) {
		wizard.jumpTo(0);
		}).appendTo(place)[0];
	this.currentStep = 0;
	this.body = $('<div class="wizardBody"/>').appendTo(place)[0];
	this.steps = steps;
	this.values = {};
	this.createStep(0);
};

WizardMaker.prototype = {
	/*
	OPTIONS
	step: [function, options]
	*/
	createStep: function(stepNumber) {
		$(this.body).empty();
		var step = this.steps[stepNumber];
		if(!step) {
			throw "invalid step (" + stepNumber + ")"
		}
		var options = step[1] || {};
		var humanStep = stepNumber + 1;
		var heading = "Step " + humanStep;
		if(options.heading) {
			heading += ": " + options.heading;
		}
		$("<h2 />").text(heading).appendTo(this.body);
		var container = $('<div class="wizardStep" />').appendTo(this.body)[0];
		step[0](container, this);
	},
	next: function() {
		if(this.currentStep < this.steps.length - 1) {
			this.currentStep += 1;
		}
		this.createStep(this.currentStep);
	},
	jumpTo: function(step) {
		this.currentStep = step;
		this.createStep(step);
	},
	setValue: function(name, val) {
		this.values[name] = val;
	},
	getValue: function(name) {
		return this.values[name];
	}
};

if(window.FileReader) {
	window.ImportWizard = function(options) {
		var proxy = options.proxy, saveFunction = options.save,
			internalizeTiddler = options.internalizeTiddler, proxyType = options.proxyType || "GET";
		return [
			[
				[function(body, wizard) {
					$(body).html('Where do you want to import from? <select><option value="1">file</option><option value="2">the web</option></select><button class="button">ok</button>');
					$("button", body).click(function(ev) {
						var opt = $("select", body).val();
						if(opt === "1") {
							wizard.next();
						} else {
							wizard.jumpTo(2);
						}
					});
				},
				{ heading: "File or Web?" }],
				[function(body, wizard) {
					$(body).html('Browse for a file: <input type="file" size="50" name="txtBrowse"><br><hr><div class="wizardFooter"><div class="message"></div></div>');
					function handleFileSelect(evt) {
						reader = new FileReader();
						reader.onerror = function(e, msg) {
							alert("Error occurred")
						};
						reader.onabort = function(e) {
							alert('File read cancelled');
						};
						reader.onload = function(e) {
							var html = reader.result;
							wizard.setValue("html", html);
							wizard.jumpTo(3)
						}
						// Read in the image file as a binary string.
						window.reader = reader;
						reader.readAsText(evt.target.files[0]);
					}
					$("[type=file]", body)[0].addEventListener('change', handleFileSelect, false);
				}, { heading: "Locate TiddlyWiki file" }],
				[function(body, wizard) {
					$(body).html('Enter the URL or pathname here: <div class="message"></div><input type="text" size="50" name="txtPath"><button class="button">open</button>');

					$("button", body).click(function(ev) {
						var url = proxy.replace("%0", $("input", body).val())
						ajaxReq({
							type: options.proxyType,
							url: url,
							success: function(html) {
								wizard.setValue("html", html);
								wizard.jumpTo(3);
							},
							error: function() {
								$(".message").html("There is something wrong with that url please try another.");
								$("input", body).addClass("error");
							}
						})
					})
				},
				{ heading: "Import from Web" }],
				[function(body, wizard) {
					var html = wizard.getValue("html");
					var doc = $(html);
					var store;
					$(html).each(function(i, el) {
						if(el.id === "storeArea") {
							store = el;
						}
					});
					if(store) {
						var tiddlers = [];
						$(store).children().each(function(i, el) {
							var title = $(el).attr("title");
							tiddlers.push(internalizeTiddler(el));
						});
						$("<div />").text("Choose tiddlers that you wish to import");
						var table = $("<table />").appendTo(body)[0];
						$("<tr />").html('<th><input type="checkbox" checked/></th><th>title</th>').
							appendTo(table)
						$("input", table).change(function(ev) {
							var checked = $(ev.target).is(':checked');
							$("input[type=checkbox]", body).attr("checked", checked);
						});
						for(var i = 0; i < tiddlers.length; i++) {
							var title = tiddlers[i].title;
							var row = $("<tr />").data("tiddler", tiddlers[i]).appendTo(table)[0];
							$("<td />").html('<input type="checkbox" checked="checked"/>').appendTo(row);
							$("<td />").text(title).appendTo(row);
						}
						$('<button class="button">import all selected tiddlers</button>').click(function(ev) {
							var tids = [];
							$("input[type=checkbox]:checked").each(function(i, chk) {
								var tiddler = $(chk).parents("tr").data("tiddler");
								if(tiddler) {
									tids.push(tiddler);
								}
							});
							wizard.setValue("selected", tids);
							wizard.jumpTo(4)
						}).prependTo(body);
					}
				},
				{ heading: "Choose tiddlers" }],
				[function(body, wizard) {
					var tids = wizard.getValue("selected");
					$(body).text("Please wait");
					// do import
					var save = 0;
					var complete = function() {
						save += 1;
						if(save === tids.length) {
							wizard.jumpTo(5);
						}
					};
					$(body).text("Please wait (Importing " + tids.length + " tiddlers)");
					for(var i = 0; i < tids.length; i++) {
						var tid = tids[i];
						$(body).text("Please wait (Importing " + tid.title + ")");
						saveFunction(tid, complete);
					}
				},
				{ heading: "Importing" }],
				[function(body, wizard) {
					$(body).html("Good news! Everything is now imported.");
				},
				{ heading: "Finished!" }]
			],
			{
				heading: "Import tiddlers from another file or server"
			}
		];
	}
} else {
  $("#container").addClass("error").text("Your browser is not modern enough to support this app.");
}

})(jQuery);
(function($) {

if(window.ImportWizard) {
	var proxy = "%0", proxyType = "GET";
	if(config.extensions.tiddlyspace) {
		proxy = "/reflector?uri=%0";
		proxyType: "POST";
	}
	var loader = new TW21Loader();
	var internalizer = function(node) {
		var title = $(node).attr("title");
		var tiddler = new Tiddler(title);
		loader.internalizeTiddler(store, tiddler, title, node);
		return tiddler;
	};

	var importer = ImportWizard({proxy:"%0", save: function(tid, callback) {
		merge(tid.fields, config.defaultCustomFields);
		delete tid.fields["server.page.revision"];
		delete tid.fields["server.etag"];
		tid = store.saveTiddler(tid.title, tid.title, tid.text,
			tid.modifier, tid.modified, tid.tags, tid.fields, null, tid.created, tid.creator);
		autoSaveChanges(null, [tid]);
		callback();
	}, internalizeTiddler: internalizer, proxyType: proxyType });

	config.macros.importTiddlers = {
		handler: function(place) {
			var container = $("<div />").appendTo(place)[0];
			new WizardMaker(container, importer);
		}
	};
} else if(config.macros.importTiddlers) {
	var _import = config.macros.importTiddlers.handler;
	config.macros.importTiddlers.handler = function(place) {
		_import.apply(this, arguments);
		jQuery("<div class='annotation error' />").text("Please upgrade your browser to take advantage of the modernised file import mechanism of the TiddlyFileImportr plugin.").prependTo(place);
	};
}

})(jQuery);
By default we track all links within a template - you don't have to do anything at all. We take your ~URLs and re-encode them so that they come via our web servers using something called a redirect. This allows to track exactly who has clicked which links in which messages, but also makes your ~URLs longer.

Some email programs do not like ~URLs over 255 characters, some will even break at 72 characters, so it's a good idea to keep your ~URLs as short as possible. We add a unique identifier (about 22 characters) that allows us to track the message that the click happened in and add it all to the URL for the redirector. The ~URLs end up like this:

{{{http://smm.im/w/<message identifier>/<original URL + 10%>}}}

Our overhead is thus about 40 characters and encoding adds some overhead to the embedded URL, so you should be able to safely use ~URLs up to about 195 characters. That said, ~URLs longer than this will still work in many clients. We truncate ~URLs at 2048 characters before encoding - if you need them longer than that, you're probably doing something wrong!

Links in plain text messages are another matter - many plain-text clients will break ~URLs over 1 line long, which is often only 72 characters. For that reason ''we don't add tracking to plain text ~URLs'' as it's more important that people get to where the link is going than that you know they have done so.

We don't track links to our unsubscribe pages (they're just not very interesting and you you can get the unsubscribe data elsewhere).

When writing your links, bear in mind that it's possible for your links to look like [[Phishing]] attempts if you're careless.

!!URL Syntax
When a message is generated for sending, we enforce [[RFC2396|http://www.faqs.org/rfcs/rfc2396.html]] section 2.4.3 compliance (the standard for URL syntax) in link ~URLs, stripping any characters that are not allowed. These are:
* All control characters (line breaks, tabs etc)
* Spaces
* Reserved delimiters: |, {, }, \, <, >, ^, ' and ".
The only character set supported in ~URLs is ~US-ASCII, as defined in ~RFC2396 and [[RFC3896|http://tools.ietf.org/html/rfc3986#page-11]]. ~UTF-8 or any other character set is ''NOT'' supported. That means //you should not expect Chinese, Russian etc characters in your ~URLs to work//. Some browsers will try their best to make these characters work (and will often display ~URL-encoded characters in their native form, so there is no harm in URL encoding), and you mught be lucky, but generally speaking, any characters like this should be ~URL-encoded.
!!Suppressing tracking
If you want to exclude a link from being tracked, just add a 'nosmtracking' keyword to the link, like this:
{{{
<a href="http://www.example.com/secretclicks.php" nosmtracking>I'm not tracked!</a>
}}}
The keyword will be removed during the send process, so your recipients will never see it, the link will not go via our redirector, and it will not appear on any reports.

If you want to add further sophistication to your link tracking (for example correlating mailshot activity with completed sales), take a look at GoogleAnalytics.

See [[open tracking|Open Tracking]] for information on tracking message openings.
!!Phishing warning
If your link points at a destination likely to be the target of phishing campaigns, such as Facebook or Paypal, you need to be careful not to use link text that looks like a URL as it may trigger anti-phishing defences. In those cases, either use the above feature to disable tracking on the URL, change the link text, or make the link go via another redirector/shortener such as [[bit.ly|https://bitly.com/]] or [[goo.gl|http://goo.gl/]].
Unsurprisingly, a mailing list is a list of recipients you wish to send email to. Smartmessages doesn't stop at just the lists themselves, but also allows you to gather data about your recipients, so your customers are stored once, and their subscriptions to multiple lists are stored separately. This means that if you know that fred@example.com actually likes to be called John, that fact will be available across all of your lists, not just one of them - your customer is your customer, not several different flavours of that customer. Most mailing list management services don't offer this independence, and suffer from fragmented and incomplete data as a result.

When you upload a mailing list, it is first filtered according to any active [[suppressions|SuppressionLists]] that may apply to it.

At present we don't have dynamic list segmentation (e.g. mail the subset of a list that responded the last time you mailed them), but it's [[something we're working on|FutureFeatures]].
[[Start here]]
[[AccountSettings]]
[[UploadLists]]
[[DownloadLists]]
[[Templates]]
[[SendingMailings]]
[[Reporting]]
[[RepeatMailings]]
[[Payment]]
[[API]]
[[ContactUs]]

All content Copyright &copy; 2014 [[Synchromedia Limited|http://www.synchromedia.co.uk/]]
Microsoft Word includes features for exporting nicely formatted documents as HTML. Unfortunately Word is to HTML what the Titanic was to safe travel. It's a total disaster and you should avoid using it if even remotely possible.
!Mitigating the disaster
Smartmessages has two places where you can set the complete HTML content of templates - in the template page and in the content editor when sending a mailshot. The former is really for those that understand and work with HTML (designers/developers), and we would not expect them to be using Word for design anyway. The latter is where you're morely likely to run into trouble. Fortunately the content editor has a special 'paste from word' button that cleans up most of the excess junk that Word adds, but it does require that you use it for it to work, and not just paste in your content as normal.

Word has two options when exporting to HTML "Web page" and "Web page, filtered". The latter option produces much better results.

There are some conversion services that can help you remove most of the bad markup from Word's HTML:
* [[WordHTMLCleaner|http://www.wordhtmlcleaner.co.uk]] cleans up HTML documents created using Word's "Web page" exporter.
* [[Textfixer.com|http://www.textfixer.com/html/convert-word-to-html.php]] provides a system for converting {{{.doc}}} or {{{.docx}}} Word documents directly into clean HTML in a much better way than Word itself does.
If you're working with templates, you'll find full details of our new template syntax in the TemplateGuide. If you've made templates for Smartmessages before, this article will tell you how to convert your syntax for the new system. It helps to be up to speed on the [[new syntax|TemplateSyntax]] before attempting your own conversion.
Generally speaking we have moved away from using one-off custom tags towards a generic way of using dynamic information in a consistent, logical way. This makes it much more obvious where data is coming from - for example, was the old tag {{{[[url]]}}} the URL of your own web site, or the URL of the recipient's personal site? It was actually the first of these, and you will now find these two possibilities as {{{[[$subscriber.url]]}}} and {{{[[$account.url]]}}} which is far clearer.
The new system moves more of the logic into your templates and makes them a little more verbose, but also gives you much more control - for example in our old system if you didn't like the facebook icons we provided, you had to hard-code everything yourself; now it will provide our icon as a default, but it's trivial for you to change it to your own image. Similarly, it's now easy to set your own text for an unsubscribe link if you don't like our default.
Another key difference is that we now support relative ~URLs for images in templates (to an extent), which keeps templates smaller and more flexible.
!Automatic conversions
During the upgrade to the new version, we will be converting all templates to use the new syntax anyway, but if you later import a template that uses old syntax, you can use the 'Convert from old format" checkbox on the templates page. We also attempt to spot old-format templates and apply this conversion automatically.
!Common elements
Many of our customers do not make much use of personalisation (you're missing a trick!), and only make use of a few common tags. Here's a table of the most common tags in old and new syntax:
|!Original markup |!New markup |!Notes|
|{{{[[informal_greeting|Reader]]}}}|{{{[[$subscriber.informal_greeting|default:"Reader"]]}}}|Makes use of {{{default}}} modifier|
|{{{[[formal_greeting|Reader]]}}}|{{{[[$subscriber.formal_greeting|default:"Reader"]]}}}||
|{{{[[firstname]]}}}|{{{[[$subscriber.firstname]]}}}||
|{{{[[lastname]]}}}|{{{[[$subscriber.lastname]]}}}||
|{{{[[opengraphtags]]}}}||No longer needed|
|{{{[[body]]}}}||No longer needed|
|{{{[[tracking_image]]}}}|{{{[[tracking_image]]}}}|No longer required - handled automatically|
|{{{[[unsubscribe_link]]}}}|{{{<a nosmtracking href="[[$mailshot.unsubscribe_url]]">unsubscribe</a>}}}||
|{{{[[unsubscribe_url]]}}}|{{{[[$mailshot.unsubscribe_url]]}}}||
|{{{[[webversion link]]}}}|{{{<a href="[[$mailshot.webversion_url]]">View web version</a>}}}||
|{{{[[webversion_url]]}}}|{{{[[$mailshot.webversion_url]]}}}||

!Complete conversion table
Here are all of our old tags and their new equivalents:
|!Original markup |!New markup |!Notes |
|{{{[[address1]]}}}|{{{[[$subscriber.address1]]}}}||
|{{{[[address2]]}}}|{{{[[$subscriber.address2]]}}}||
|{{{[[address3]]}}}|{{{[[$subscriber.address3]]}}}||
|{{{[[backcolour]]}}}|{{{[[$account.backcolour]]}}}||
|{{{[[body]]}}}||No replacement - edit content directly|
|{{{[[bodyfonts]]}}}|{{{[[$account.bodyfonts nofilter]]}}}||
|{{{[[campaign_id]]}}}|{{{[[$campaign.id]]}}}||
|{{{[[campaign_name]]}}}|{{{[[$campaign.name]]}}}||
|{{{[[companyname]]}}}|{{{[[$subscriber.companyname]]}}}||
|{{{[[country]]}}}|{{{[[$subscriber.country]]}}}||
|{{{[[county]]}}}|{{{[[$subscriber.county]]}}}||
|{{{[[custom1]]}}}|{{{[[$subscriber.custom1]]}}}|Similarly for custom2 - custom32|
|{{{[[customer_address]]}}}|{{{[[$account.address|nl2br nofilter]]}}}||
|{{{[[customer_companynumber]]}}}|{{{[[$account.companynumber]]}}}||
|{{{[[customer_email]]}}}|{{{[[$account.email]]}}}||
|{{{[[customer_name]]}}}|{{{[[$account.name]]}}}||
|{{{[[customer_phone]]}}}|{{{[[$account.phone]]}}}||
|{{{[[customer_url]]}}}|{{{[[$account.url]]}}}||
|{{{[[dear]]}}}|{{{[[$subscriber.dear]]}}}||
|{{{[[dob]]}}}|{{{[[$subscriber.dob]]}}}||
|{{{[[email]]}}}|{{{[[$subscriber.email]]}}}||
|{{{[[facebooklike]]}}}|{{{[[facebook_like]]}}}||
|{{{[[facebookpagebutton]]}}}|{{{[[if $account.facebook]]<a href="[[$account.facebook_pageurl]]"><img src="/library/socialmediaicons/facebook_32.png" height="32" width="32" border="0" alt="Visit our facebook page"></a>[[/if]]}}}||
|{{{[[facebookpageurl]]}}}|{{{[[$account.facebook_pageurl]]}}}||
|{{{[[firstname]]}}}|{{{[[$subscriber.firstname]]}}}||
|{{{[[footer]]}}}||No replacement|
|{{{[[forecolour]]}}}|{{{[[$account.forecolour]]}}}||
|{{{[[formal_greeting|Subscriber]]}}}|{{{[[$subscriber.formal_greeting|default:"Subscriber"]]}}}||
|{{{[[googleplus]]}}}|{{{[[googleplus_share]]}}}||
|{{{[[googlepluspagebutton]]}}}|{{{[[if $account.googleplus]]<a href="[[$account.googleplus_pageurl]]"><img src="/library/socialmediaicons/google_32.png" height="32" width="32" border="0" alt="Visit our Google+ page"></a>[[/if]]}}}||
|{{{[[googlepluspageurl]]}}}|{{{[[$account.googleplus_pageurl]]}}}||
|{{{[[gravatar16]]}}}|{{{[[gravatar size=16]]}}}||
|{{{[[gravatar32]]}}}|{{{[[gravatar size=32]]}}}||
|{{{[[gravatar64]]}}}|{{{[[gravatar size=64]]}}}||
|{{{[[header]]}}}||No replacement|
|{{{[[headingfonts]]}}}|{{{[[$account.headingfonts nofilter]]}}}||
|{{{[[imagebase_url]]}}}|{{{[[$account.imagebase_url]]}}}||
|{{{[[informal_greeting|Subscriber]]}}}|{{{[[$subscriber.informal_greeting|default:"Subscriber"]]}}}||
|{{{[[initials]]}}}|{{{[[$subscriber.initials]]}}}||
|{{{[[jobtitle]]}}}|{{{[[$subscriber.jobtitle]]}}}||
|{{{[[landing_url]]}}}|{{{[[$account.landingpage_url]]}}}||
|{{{[[lastname]]}}}|{{{[[$subscriber.lastname]]}}}||
|{{{[[linkedin]]}}}|{{{[[linkedin_share]]}}}||
|{{{[[linkedinpagebutton]]}}}|{{{[[if $account.linkedin]]<a href="[[$account.linkedin_pageurl]]"><img src="/library/socialmediaicons/linkedin_32.png" height="32" width="32" border="0" alt="Visit our LinkedIn page"></a>[[/if]]}}}||
|{{{[[linkedinpageurl]]}}}|{{{[[$account.linkedin_pageurl]]}}}||
|{{{[[linkedinshare]]}}}|{{{[[linkedin_share]]}}}||
|{{{[[logo]]}}}|{{{[[$account.logo]]}}}||
|{{{[[logourl]]}}}|{{{[[$account.logo_url]]}}}||
|{{{[[mailinglist_id]]}}}|{{{[[$mailinglist.id]]}}}||
|{{{[[mailinglist_name]]}}}|{{{[[$mailinglist.name]]}}}||
|{{{[[mailshot_id]]}}}|{{{[[$mailshot.id]]}}}||
|{{{[[mailshot_name]]}}}|{{{[[$mailshot.name]]}}}||
|{{{[[mailshot_timestamp]]}}}|{{{[[$mailshot.timestamp]]}}}||
|{{{[[message_timestamp]]}}}|{{{[[$message.timestamp]]}}}||
|{{{[[mobile]]}}}|{{{[[$subscriber.mobile]]}}}||
|{{{[[opengraphtags]]}}}||No longer needed|
|{{{[[options_url]]}}}|{{{[[$subscriber.options_url]]}}}||
|{{{[[phone]]}}}|{{{[[$subscriber.phone]]}}}||
|{{{[[postcode]]}}}|{{{[[$subscriber.postcode]]}}}||
|{{{[[posttown]]}}}|{{{[[$subscriber.posttown]]}}}||
|{{{[[privacy_url]]}}}|{{{[[$account.privacypolicy_url]]}}}||
|{{{[[rss_*]]}}}||Replaced by new [[RSS]] tag|
|{{{[[rss]]}}}||Replaced by new [[RSS]] tag|
|{{{[[sendtofriend]]}}}|{{{<a href="mailto:?subject=[[$message.subject|default:$account.name|escape:'url']]&amp;body=[[$mailshot.webversion_url|escape:'url']]>Send to a friend</a>}}}||
|{{{[[sex]]}}}|{{{[[$subscriber.sex]]}}}||
|{{{[[staticshareurl_facebook]]}}}|{{{[[$mailshot.facebook_staticshareurl]]}}}||
|{{{[[staticshareurl_linkedin]]}}}|{{{[[$mailshot.linkedin_staticshareurl]]}}}||
|{{{[[staticshareurl_twitter]]}}}|{{{[[$mailshot.twitter_staticshareurl]]}}}||
|{{{[[styles]]}}}||No replacement|
|{{{[[subject]]}}}|{{{[[$message.subject|default:$account.name]]}}}||
|{{{[[subscribe_link]]}}}|{{{<a href="[[$mailinglist.subscribe_url]]">subscribe</a>}}}||
|{{{[[subscribe_url]]}}}|{{{[[$mailinglist.subscribe_url]]}}}||
|{{{[[title]]}}}|{{{[[$subscriber.title]]}}}||
|{{{[[tracking_image]]}}}|{{{[[tracking_image]]}}}|No longer explicitly needed in templates|
|{{{[[tracking_url]]}}}|{{{[[$message.tracking_url]]}}}||
|{{{[[twitter]]}}}|{{{[[twitter_share]]}}}||
|{{{[[twitterfollow]]}}}|{{{[[twitter_follow]]}}}||
|{{{[[twitterpagebutton]]}}}|{{{[[if $account.twitter]]<a href="[[$account.twitter_pageurl]]"><img src="/library/socialmediaicons/twitter_32.png" height="32" width="32" border="0" alt="Visit our twitter page"></a>[[/if]]}}}||
|{{{[[twitterpageurl]]}}}|{{{[[$account.twitter_pageurl]]}}}||
|{{{[[unique_message_id]]}}}|{{{[[$message.id]]}}}||
|{{{[[unique_recipient_id]]}}}|{{{[[$subscriber.id]]}}}||
|{{{[[unsubscribe_link]]}}}|{{{<a nosmtracking href="[[$mailshot.unsubscribe_url]]">unsubscribe</a>}}}||
|{{{[[unsubscribe_url]]}}}|{{{[[$mailshot.unsubscribe_url]]}}}||
|{{{[[url]]}}}|{{{[[$subscriber.url]]}}}||
|{{{[[urlencoded_email]]}}}|{{{[[$subscriber.email|escape:'url']]}}}||
|{{{[[webversion link]]}}}|{{{<a href="[[$mailshot.webversion_url]]">View web version</a>}}}||
|{{{[[webversion_link_personal]]}}}|{{{<a href="[[$message.webversion_url]]">View web version</a>}}}||
|{{{[[webversion_url_personal]]}}}|{{{[[$message.webversion_url]]}}}||
|{{{[[webversion_url]]}}}|{{{[[$mailshot.webversion_url]]}}}||
|{{{[[webversion]]}}}|{{{[[$mailshot.webversion_url]]}}}||
Smartmessages' new templating system has been a long time coming, but now it's here and ready to produce better results than ever before. So what's changed?
!How things used to be
Previously, we used a complex structure for building reusable templates where separate editors were provided for isolated areas of a host template - you couldn't edit the overall template itself when creating a mailshot, only the content areas. This was a mixed blessing: it prevented you from breaking your overall layout, but meant you couldn't really see what was going on, and while the overall layout could include personalisation tags, the editable content areas could not. This dual-layer approach promoted template re-use but we found that most of our customers didn't take advantage of it, and the creation of these editable sections was something that we had to do for you - it wasn't a public feature, apart from the limited {{{[[body]]}}} tag available in the template editor. It was also quite slow.

The tagging system was inconsistent and somewhat inflexible - for example we needed two separate tags to support an email address and a ~URL-encoded email address; the only tags that supported a default value were {{{[[informal_greeting]]}}} and {{{[[formal_greeting]]}}}, yet there were many other tags that would have benefited from options like these.

There was no opportunity for conditional content - displaying certain sections of your template only to specific recipients matching certain criteria.

There was no error reporting mechanism for template markup - if you made a mistake it would be silently ignored - that makes for a quiet life, but could result in mailings going wrong.

We had RSS support, but it wasn't easy to use, and wasn't very flexible.

This might sound bad, but it's been a workable system for many years, most of our customers didn't have much difficulty with these limitations, and it's broadly similar to what many of our competitors offer.
!The new system
!!Internal reorganisation
The new system is still built using the same [[Smarty|http://www.smarty.net/]]-based template engine, but the main change is that we've done away with the dual-layer templates. This alone improves performance substantially.
!!Mailshots independent of templates
Mailshots are no longer tightly-bound to the templates they use, so templates can be deleted without deleting the mailshots that have used them. A mailshot now takes a snapshot of the template when select it and then allows you to edit every aspect of it without impacting other mailshots that use the same template, and changing templates no longer has the ability to alter scheduled mailshots unexpectedly.
!!Simplified sending
The sending process has been simplified, requires fewer steps, and you get to see your whole message at once when editing it.
!!Instant testing
While you're editing, you can instantly send a test mailing (without stopping what you're doing) with just one click.
!!Powerful editing
Tag insertion is integrated right into the (newly updated) WYSIWYG editor, The editor knows what tags look like and helps prevent you making mistakes when editing.
!!Faster feedback
Previews do not require page reloads or new windows. Saving is automatic, and all the template testing features (link, image, spam, validation etc) that were previously only available to those building their own templates are now available to apply to your mailshots in one click.
!!Consistent tagging
Tag names, variables and elements are now neatly organised in a logical, consistent way, meaning you're more likely to be able to guess tag names, less likely to make mistakes, and the availability of modifiers gives you great control over tag output. Automatic escaping makes HTML corruption far less likely.
!!New tags
As well as a general clean-up of tag naming, there are some great new tags: a table of contents ({{{[[toc]]}}}) tag generates automatic intra-page links. The newly rewritten RSS support is far more flexible, and allows you to embed multiple feeds in one template. Good-looking image-free buttons are quick and easy to generate with the new {{{[[button]]]}}} tag.
!!Conditional content
Now you can make sections of your templates only visible to certain recipients, based on any data you have using {{{[[if]]}}} tags. This is a powerful way of providing ultimate personalisation, but also allows content that only shows on web versions - for example you can encourage new visitors to sign up without annoying those that already have.
!!Honest error reporting
We now provide detailed, accurate error reports on both template and mailshot pages, and you won't be able to send a mailshot if its template contains errors.
!!New social networking features
Share links are now super-easy to create, just a {{{[[share]]}}} away, compete with shiny new retina-ready icons!
!!New templates
A new template system wouldn't be complete without some new templates to exploit it! Our new standard, simple templates are built using Zurb's fantastic responsive email framework, [[Ink|http://zurb.com/ink/]]. Another benefit of the all-at-once approach is that we have much better compatibilty with templates imported from elsewhere. We're planning on adding conversion options for some competitors' formats that will make migration much quicker and easier.
!!Image library
Over the years we have buit up a library of images that are useful when building emails - for example social network icons and separators. This library is now available when you insert an image, so you can browse our library without having to worry about finding and uploading your own, though of course you can still do that too!
!How do the changes affect me?
The main way this affects you is that the template tags have changed, which means that your existing templates won't work. Fortunately we're not about to leave you dangling, so we have converted all your existing templates (and the mailshots that used them) to the new system. This does mean that you'll need to read our [[new template guide|TemplateGuide]] when you're building new templates from now on, but you should find that everything works as before - the "copy & edit" workflow remains the same.
!!Mailshots now use template snapshots
It's important to understand the new independence of mailshots from templates - editing a template after creating a mailshot that uses it will no longer change the mailshot. Don't expect the mailshot editor (the send page) to display a 'current template', because that concept no longer exists. If you change the template for a mailshot you're working on and want it updated, re-select it from the template picker.
!!Converting your own templates
If you've been using our old template syntax, you'll need to change your workflows and any snippets you may use - take a look at the [[New Template Conversion]] article for more details.
!!Custom built templates
Some of our customers have custom-built templates; These are the ones most affected by the template changes. If you copy & edit mailshots using templates that had large numbers of editable areas but that were not actually used, you will find you may get some empty content blocks that need deleting (and you'll typically be doing that anyway if you've copied a previous mailshot), but this does not affect new mailshots, nor does it affect web versions of previous mailshots.
!!API
The [[API|API]] itself is unchanged - but the templates that you use with it will need to use the new format.
!Help!
As always, we're here to help - we appreciate that there are big changes in here that may cause you some issues, so please [[contact us|ContactUs]] if there's anything you need help with.
If you want to track the opening of your messages, you need what's called a "web bug" or "beacon" image. This delivers a transparent 1-pixel GIF image, but also tells us exactly which message has been opened, when, and by whom. When using our standard templates, you don't need to do anything, but when creating your own you have a choice of two function tags. When you use a template it will automatically have a tracing image inserted just before the closing body tag.

If you don't like the image tag that we generate for you, or it cause layout issues, you can create your own using the {{{[[$message.tracking_url]]}}} tag and it will be used instead of our automatic tag.
{{{
<img src="[[tracking_url]]" width="1" height="1" alt="*">
</body>
</html>
}}}
Either way, make sure that your HTML editor doesn't rewrite the tags with URL encoding as that will break the links.
!Oh no! I forgot to put a tracker in!
Don't worry - we do it for you automatically.

There is another factor that comes into play if the recipient has images disabled: if we receive a clickthrough from a message that we do not already have an opening logged for, we will log an open (just one) as well, as it's not possible for a click to happen from a message that was never opened - even with images turned off. If you have done this, the usual symptom is that number of reported clicks and opens will typically be very similar, whereas you would normally expect opens to be much higher than clicks.

Note that we do not attempt to support tracking opening of plain-text templates, because obviously they don't support images. Some mailing companies claim that it's possible to do this - it's not; they're lying!

See [[link tracking|Link Tracking]] for information on tracking clickthroughs.
<!--{{{-->
<div class='header' role='banner'>
<a href="http://info.smartmessages.net/"><img src="http://www.smartmessages.net/images/smartmessages-logo-lt.svg" width="400" alt="Smartmessages logo" style="float:right;margin:4px 4px 0 0;"></a>
<div class='headerShadow'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
</div>
<div id='mainMenu' role='navigation' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' role='navigation' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' role='complementary' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea' role='main'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
There are two ways of paying for Smartmessages - Self-service or contract. Most of our customers take the self-service route which allows you to control your account entirely yourself. Those with more complex needs, large lists (> 100,000) or those who prefer alternative payment arrangements (such as monthly/quarterly invoice) should [[contact us|ContactUs]].

When choosing your subscription level, you should bear in mind [[what the account limits mean|Account limits and quotas]].

Click on the "Account" tab. [IMG[images/account1.gif]]

Click on the "Buy Smartmessages services" tab [IMG[images/smtab1.gif]]

Choose the service you require, click the ~PayPal button next to the service you want. You'll be taken to ~PayPal. Either log into your account, or use the credit card payment service they offer.

Generally Smartmessages uses ~PayPal's subscription services, so your payments will happen automatically every month without you having to do anything.

Phishing attempts to trick users into revealing personal and financial details, often by making web sites that pretend to be sites such as eBay, Paypal and banks, but it can occur for any site that typically asks for personal details. These usually take the form of email messages saying your account has had some kind of problem and that you should log in to the site and update your password; the link they provide will take you to a site that looks like your bank's normal login page, but is actually a fake just looking to steal your passwords. This is clearly a bad thing.

Smartmessages includes support for anti-phishing measures. We provide an opportunity for our customers to include a link back to us (on the account settings page), and we use that link wherever we ask for a subscriber's details, so tey can check that we are legitimately acting for a customer, and not attempting to obtain data for nefarious purposes.

To be safe from phishing, you should check that email messages really come from where they say they do, and that any domain name (the 'example.com' part of 'user@example.com') used in an email is the same as the anti-phishing page our customer has posted. We also recommend tha you use and email provider that supports [[SPF]] and [[DKIM|DKIM and DomainKeys]] checks against incoming mail.
!!Phishing links
One common feature of phishing emails is the use of links which pretend to go to somewhere you may want, but actually go elsewhere. For example they may invite you to log in to somewhere common, say Facebook, and provide a link to a page that looks like a Facebook login page, which fails to log you in, but by then you've given them your ID and password...

Unfortunately it's quite easy to do this by accident. If you create a link in your HTML that looks like this:
{{{
<a href="http://www.example.com/">http://www.example.com/</a>
}}}
That's innocuous as it stands, however, because we add redirects in order to implement link tracking, it is dynamically rewritten to something like:
{{{
<a href="http://smm.im/w/1obySedyob56cN0N1jGjiC3/http%3A%2F%2Fwww.example.com%2F">http://www.example.com/</a>
}}}
and now we have the text of the link looking like one URL, but the link going somewhere else, i.e. something that could be considered a possible phishing attempt. When you're creating templates, links like this will be warned about in the Link report available in the template editor.

There are two simple workarounds for this:
* ''Don't use ~URLs as link text'' - they are not really intended to be human-readable, and unless your audience are web developers, you're not telling them anything they want to know.
* Use the Smartmessages {{{nosmtracking}}} attribute to suppress link rewriting, as described in our [[Link Tracking]] documentation.
A "pre-header" is a jargon term for a small piece of text that appears at the very start of your message, before any main header or content section. It is interesting because quite a few significant clients (notably Apple Mail, iOS mail, Gmail and Outlook.com) display the first bit of the message in addition to your subject line as part of a kind of mini-preview of each message. HTML tags are ignored, only the actual text is used. By default this is often something like 'if you can't see this message, open this link in your browser', or 'click here to unsubscribe'. Neither of those are especially friendly or useful at this point so it's common to place some additional text up front (the pre-header) so that it's used as the preview. You might think this may make a mess of your layout, but fortunately this text is still displayed in the preview even if it is hidden using CSS. A typical pre-header might look like this:
{{{
<span style="display:none !important;">This week's hints and tips from your favourite supplier.</span>
}}}
You can add one of these manually or by picking the pre-header snippet from the template snippets list in the mailshot editor. It should be the very first thing in your content.
!Automating pre-header generation
A good way of making your pre-header generate itself automatically (so you don't have to update it every time you send a mailshot) is to use our {{{[[toc]]}}} table of contents tag, like this:
{{{
<div style="display:none !important;">[[toc start=1 levels=1 flat=true]]</div>
}}}
This will extract all {{{h1}}} headers with ids in your message and use them as a pre-header.
RSS stands for [[Really Simple Syndication|http://en.wikipedia.org/wiki/Rss]], which is a micro-publishing mechanism for web sites and other content generating sources. RSS is also used as a general term covering the common RSS 1.0, 2.0 and [[Atom|http://en.wikipedia.org/wiki/Atom_(standard)]] publishing standards.

An RSS feed generally contains a few bits of info about the feed itself, such as its title and where it's coming from, and a collection of items constituting the feed content, usually news stories or blog posts, along with their titles, publishing date, author, category, and a link to the source of the item. They may contain other metadata elements too, but those are the common ones. The content elements may contain complete content (known as a "full feed") or just a summary or introductory paragraph.

Many people like using RSS feeds as they are typically free of ads, provide clutter-free offline reading (ideal for mobile use), and allow the reader (rather than the web site owner) to decide how they are presented. An RSS reader application will manage multiple feeds, group them into categories and update them regularly, and such functionality is often integrated into browsers (as it is in Internet Explorer, Safari, Opera and others). The usual pattern is that a feed is downloaded, summaries are read, and links to full content (e.g. the original blog post) are followed.

Most content management systems and blogs have the ability to publish RSS feeds of their content which may be consumed by anyone that's interested. A request for an RSS feed is often accompanied by additional parameters that might allow the consumer to specify a date range, a maximum number of items to return, one or more category selections, summarised or full content and so on. As an example, [[this news page at the BBC|http://www.bbc.co.uk/news/]] is also available as [[an RSS feed|http://feeds.bbci.co.uk/news/rss.xml]].
!Why use RSS?
The upshot of all this is that it's possible to create programs that consume the feeds and repurpose them - so you might pull the top 5 stories from your company blog and use them to populate your monthly newsletter in Smartmessages, saving you a lot of copy and paste in the production of your newsletter!
!Using RSS in Smartmessages
Smartmessages supports RSS feeds in [[templates|TemplateGuide]] via a template function tag called (no surprises!) {{{[[rss]]}}}. This is a complex block tag that also expects a closing {{{[[/rss]]}}} tag.
!!Tag parameters
The rss tag accepts these parameters:
|!Parameter |!Purpose |!Default |
| {{{url}}} |The URL of the feed||
| {{{items}}} |The number of items to fetch from the feed (1-25, feed contents permitting)| {{{5}}} |
| {{{wordlimit}}} |Number if words to limit each story to.|No limit|
The {{{wordlimit}}} option is used to limit the size of content sections of stories in a feed (in {{{[[$rssitem.content]]}}} elements) - some "full feed" feeds just provide too much content, so this lets you truncate stories to a manageable length. We recommend starting with 50 as a minimum as it includes HTML markup in the word count; HTML filters will tidy up unclosed tags that are left open by this process.
!Variables
Within the tag, two array variables are defined:
|!Variable | !Element |!Purpose |
| {{{$rss}}} ||The feed itself|
|| {{{title}}} |The title of the feed|
|| {{{url}}} |The URL of the feed|
|| {{{description}}} |A description of the feed |
|| {{{itemcount}}} |The number of items provided by the feed|
| {{{$rssitem}}} ||Repeatedly evaluated to contain each item in the feed|
|| {{{title}}} |The item title|
|| {{{url}}} |The URL of the article's page|
|| {{{content}}} |The actual item contents (usually includes HTML markup)|
|| {{{published}}} |A unix timestamp of the publication date and time|
|| {{{updated}}} |A unix timestamp of the most recent update date and time|
|| {{{authorname}}} |The name of the item's author|
|| {{{authoremail}}} |The email address of the item's author|
|| {{{authorurl}}} |A URL referencing the item's author|
|| {{{category}}} |A category string for the item, e.g. 'news', 'science', 'recipes'|
|| {{{first}}} |Whether this is the first item in the feed|
|| {{{last}}} |Whether this is the last item in the feed|
|| {{{index}}} |The number of the item within the feed (first item has index 0)|

It's important to realise that RSS feeds vary enormously, and many of these properties may be unpopulated (empty), though we have tried to consolidate the different feed formats (for example Atom feeds typically don't include a category, RSS 1.0 feeds don't include an update date), so if you're using an element, it's important to test it with your intended feed to make sure it looks right. Most important of these consolidations is that we always make sure that {{{[[$rssitem.content]]}}} contains the item contents, even though the feed may place it in summary or description fields internally. Content is heavily filtered for security (so scripts and other problematic content is removed), but this means that it's safe to display the HTML content provided by the feed.

A simple RSS feed template might look like this:
{{{
[[rss url="http://feeds.bbci.co.uk/news/rss.xml" items=3]]
[[if $rssitem.first]]<h2 id="rsstitle">The latest from <a href="[[$rss.url]]">[[$rss.title]]</a></h2>[[/if]]
<h3 id="rssitem[[$rssitem.index]]">[[$rssitem.title]]</h3>
<div>
[[$rssitem.content nofilter]]
<p><a href="[[$rssitem.url]]">Read more...</a></p>
<p><em>Published [[$rssitem.published|date_format:"%c"]]</em></p>
</div>
[[/rss]]
}}}
This template is rendered in a loop between the opening and closing tags, changing the contents of the {{{$rssitem}}} array each time around. In this example you can see it's using the {{{$rssitem.first}}} property to display the title of the feed the first time around the loop. Although we say above that the HTML content is safe to use, it is escaped by default, so you need to apply the {{{nofilter}}} option when displaying {{{$rssitem.content}}} to allow the HTML to render. The {{{$rssitem.published}}} timestamp is using the {{{date_format}}} modifier to turn the unfriendly unix timestamp into a more presentable form. IDs are assigned to the item titles so that they can be picked up by the {{{toc}}} table of contents tag.

You can place multiple RSS feeds in your templates using different ~URLs and item counts in each tag.
The quickest way to send a new mailshot that's similar to one you've done before is to make a copy and edit it, for example to send this month's newsletter, you might copy last month's and edit it. This can save quite a bit of work.

Choose the mailings tab: [IMG[images/mailings.gif]]

Click the copy button for the mailing you wish to use as the basis for next mailing. [IMG[images/mailings2.gif]]

Click the Edit button for the new mailshot.[IMG[images/edit.gif]]

This will take you to the send page populated with the details from the original mailing.
Click on the ''mailings'' tab: [IMG[images/mailings.gif]]

Choose a mailing, create a new campaign or download a campaigns results to open in Excel.

 [IMG[images/mailings3.gif]]

Click on the name of the mailing or a ''report'' button to see results.

Alternatively, click on the ''reports'' tab: [IMG[images/reports.gif]]

See the last viewed results, choose the required mailing from the popup list. View results.
SPF is the [[Sender Policy Framework|http://www.openspf.org/]], a mechanism used for preventing forgery of from addresses in email, and because of its strength in proving you are who you say you are, improves deliverability as filters are less likely to think that you are sending spam or phishing emails (and conversely, if you are sending spam, they can block you more effectively). It's an increasingly important weapon in the war against spam, it's used by most of the big email providers (AOL, Hotmail/MSN/Windows Live, Yahoo!, ~GMail, etc) and we recommend full support for it.
!!How does it work?
When you send a message through Smartmessages, it will be sent by one of our mail servers. Normally, there's nothing to confirm that we're not just pretending to send legitimate messages from you, and because of that, servers receiving these messages may treat them with suspicion. You can fix this by naming us as a source of email for your domain in an SPF record, which is part of your domain name (DNS) setup. Read the [[OpenSPF site|http://www.openspf.org/]] for info on how to do that - the key thing is that you should add us to your SPF record, which will allow us to send mail from your domain.
!!The technical bit
Read the [[OpenSPF|http://www.openspf.org/]] docs and/or use their wizard to create an appropriate SPF string, then insert it into your DNS as a TXT record.
A fail-safe SPF record that includes us is:
{{{
v=spf1 a mx include:smartmessages.net ?all
}}}
This translates to: //Explicitly allow sending from this domain, its mail servers and Smartmessages. For everyone else, give a neutral response.// Ideally you should aim for a {{{-all}}} (default to 'fail' rather than 'neutral' status), but don't set that until you're sure you have all your mail sources covered, and even then it may cause issues with messages sent via relay servers that do not implement SRS. //If you're using [[DMARC|http://www.dmarc.org/]], don't use {{{-all}}}, use {{{~all}}}, which delegates the PASS/FAIL decision to DMARC//.
Once you've set it up, you can check it (and everything else in your DNS) is working using a service like [[dnsreport|http://dnsreport.com/]]. If you have a Unix/Linux/~MacOS X command line handy, you can try {{{dig txt example.com}}} to check that your DNS is publishing your record correctly.
You can do SPF lookups [[here|http://www.openspf.org/Why?showform=1]]. Enter one of our IP addresses (e.g. {{{93.93.128.140}}}) and your proposed from address. Ideally you want a 'Pass' result, though 'Neutral' will work. If you get a 'Fail' or 'Softfail' response, we will not be able to send from that address reliably for you.
If you want to be an especially good citizen in your SPF records, it's nice to provide IP addresses rather than hostnames as it saves the receiving mail server a DNS lookup. For example instead of using {{{a}}} or {{{a:www.example.com}}}, specify the actual IP, like {{{ip4:123.123.123.123}}}. The terms in an SPF record are evaluated left to right, so put the most likely matches first. You can do similar things for your MX record, though if you use a commercial incoming mail service like messagelabs, we suggest you stick with a regular {{{mx}}} rather than enumerating their ~IPs. They may also have an SPF include option you can use, so check their support pages.
We recommend that you don't enumerate our server ~IPs in your SPF, but use the include delegation we suggest as then we'll retain the ability to reconfigure our own servers without having to bother you with updates. Our own SPF uses a {{{-all}}} default, so we don't represent a back-door to neutral responses.
!!Are there any downsides?
One in particular: If you commonly send email through your ISP or web mail provider using your own domain as the from address (i.e. you are in the habit of forging your own address!), you have three options:
#Use the '?all' default action (which doesn't do anything for spam prevention)
#Include the ~IPs of your ~ISPs mail servers (they should be able to supply them, but it means that anyone else at your ISP can forge mail from you)
#Alter your sending settings so you only send through your own mail servers (the most effective, but least flexible way). If your ISP blocks or redirects SMTP traffic on port 25, you may be able to get to your mail server using a submission port which is typically on port 587 and tends not to get blocked; the submission protocol usually stipulates that you use both authentication and SSL encryption too, so it's a good security measure.
Another downside is that even if you go to the effort of implementing SPF, but can't get as far as a {{{-all}}} default policy, it's not going to gain you very much since even the most brazen forger will only merit a {{{SOFTFAIL}}} response.
!!Google's Blacklisting Policy
Google has a very simple approach to SPF failures: messages are rejected, and they automatically add you to the [[CBL blacklist|http://cbl.abuseat.org/]], which is used by hundreds of ~ISPs. So a single message with an incorrect SPF setup could get you (and us) blacklisted from many ~ISPs. We plan to enhance our send-time SPF checks to ensure you can't send if your from address will cause an SPF failure.
!!What about ~SenderID?
[[SenderID|http://www.microsoft.com/mscorp/safety/technologies/senderid/default.mspx]] is a Microsoft-backed extension of SPF. It has some [[well documented pitfalls|http://www.openspf.org/SPF_vs_Sender_ID]] and is generally disliked, and even Microsoft no longer uses it itself on their email sites (Hotmail, live.com, outlook.com, office365). If you need it for some compliance reason, the most straightforward way to implement ~SenderID is to set up your SPF record and then delegate to it. This way you will only have one record to maintain (your SPF) to identify your legitimate mail sources. To do this, create another TXT record containing this text:
{{{
spf2.0/pra ?all
}}}
And that's it. Having done this, you should [[register your use of SenderID|https://support.msn.com/eform.aspx?productKey=senderid&page=support_senderid_options_form_byemail&ct=eformts]] on Microsoft's site to ensure they use it.
!!What about ~DomainKeys and DKIM?
While SPF helps to increase trust that a message's //origin// is where it says it is, DKIM and Domainkeys serve to show that a message's //content// has not been altered in transit, so the two go hand-in-hand. The way it works is fairly complex (using public-key cryptographic signatures), however the practicalities are quite straightforward. If you're already altering your DNS to add our SPF, then we thoroughly recommend that you set up DKIM as well. [[Read our guide|DKIM and DomainKeys]] on what it means and how to do it.
!!Example of a forgery rejection
Someone on the Turkish ~MarsNet DSL service attempted to send a message that claimed it was sent from our own mail server. Not surprisingly, when an SPF check was done by a Turk Telecom mail server (thank you Turk Telecom!), the message was rejected because our SPF record correctly identified it as a forgery. You can see that amongst the headers is a link to a page which will explain why the message was rejected. This is absolutely what SPF is intended to achieve, and here you can see it working perfectly.
{{{
This is an informative message sent by mail.atamedya.com.
The server was not able to deliver your email message to the following addresses:

<sales@synchromedia.co.uk> (mail.synchromedia.co.uk: 550 See http://spf.pobox.com/why.html?sender=postmaster@synchromedia.co.uk&ip=77.92.158.139&receiver=mail.synchromedia.co.uk (#5.7.1))Reporting-MTA: dns; mail.atamedya.com
Arrival-Date: Thu, 16 Jul 2009 12:01:44 +0300

Final-Recipient: rfc822;sales@synchromedia.co.uk
Action: failed
Status: 5.1.1
Remote-MTA: mail.synchromedia.co.uk
Diagnostic-Code: SMTP; 550 See http://spf.pobox.com/why.html?sender=postmaster@synchromedia.co.uk&ip=77.92.158.139&receiver=mail.synchromedia.co.uk (#5.7.1)
Received: from synchromedia.co.uk ([81.215.222.134])
	by mail.atamedya.com (Kerio MailServer 6.1.4)
	for sales@synchromedia.co.uk;
	Thu, 16 Jul 2009 12:01:42 +0300
From: "Post Office" <postmaster@synchromedia.co.uk>
To: sales@synchromedia.co.uk
Subject: 
Date: Thu, 16 Jul 2009 12:01:11 +0300
MIME-Version: 1.0
Content-Type: multipart/mixed;
	boundary="----=_NextPart_000_0014_D23B030D.A4D8B533"
X-Priority: 3
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2600.0000
X-MIMEOLE: Produced By Microsoft MimeOLE V6.00.2600.0000
}}}
/***
|Name|SaveAsPlugin|
|Source|http://www.TiddlyTools.com/#SaveAsPlugin|
|Documentation|http://www.TiddlyTools.com/#SaveAsPluginInfo|
|Version|2.7.1|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|Save current document to another path/filename|
!!!!!Documentation
<<<
see [[SaveAsPluginInfo]]
<<<
!!!!!Revisions
<<<
2011.02.14 2.7.1 fix OSX error: use picker.file.path
2009.10.13 2.7.0 added 'here' param (saves current tiddler)
2009.08.16 2.6.2 fixed handling for backstage
| Please see [[SaveAsPluginInfo]] for additional revision details |
2006.02.03 1.0.0 Created
<<<
!!!!!Code
***/
//{{{
version.extensions.SaveAsPlugin= {major: 2, minor: 7, revision: 1, date: new Date(2011,2,14)};

config.macros.saveAs = {
	label: 'save as...',
	labelparam: 'label:',
	prompt: 'Save current document to a different path/file',
	promptparam: 'prompt:',
	filePrompt: 'Please select or enter a target path/filename',
	targetparam: 'target:',
	defaultFilename: 'new.html',
	filenameparam: 'filename:',
	currfilekeyword: 'here',
	typeparam: 'type:',
	type_TW: 'tw', type_PS: 'ps', type_TX: 'tx', type_CS: 'cs', type_NF: 'nf', // file type tokens
	type_map: {
		tiddlywiki:'tw', tw:'tw', wiki: 'tw',
		purestore: 'ps', ps:'ps', store:'ps',
		plaintext: 'tx', tx:'tx', text: 'tx',
		comma:     'cs', cs:'cs', csv:  'cs',
		newsfeed:  'nf', nf:'nf', xml:  'nf', rss:'nf'
	},
	limitparam: 'limit:',
	replaceparam: 'replace',
	mergeparam: 'merge',
	quietparam: 'quiet',
	openparam: 'open',
	askParam: 'ask',
	hereParam: 'here',
	askMsg: "Enter a tag filter (use * for all tiddlers, 'none' for blank document)",
	hereMsg: 'Enter a tiddler title',
	emptyParam: 'none',
	confirmmsg: "Found %0 tiddlers matching\n\n'%1'\n\nPress OK to proceed",
	mergeprompt: '%0\nalready contains tiddler definitions.\n'
		+'\nPress OK to add new/revised tiddlers to current file contents.'
		+'\nPress Cancel to completely replace file contents',
	mergestatus: 'Merged %0 new/revised tiddlers and %1 existing tiddlers',
	okmsg: '%0 tiddlers written to %1',
	failmsg: 'An error occurred while creating %1',
	filter: '',
	handler: function(place,macroName,params) {
		if ((params[0]||'').startsWith(this.labelparam))
			var label=params.shift().substr(this.labelparam.length);
		if ((params[0]||'').startsWith(this.promptparam))
			var prompt=params.shift().substr(this.promptparam.length);
		if ((params[0]||'').startsWith(this.targetparam))
			var target=params.shift().substr(this.targetparam.length);
		if ((params[0]||'').startsWith(this.filenameparam))
			var filename=params.shift().substr(this.filenameparam.length);
		if ((params[0]||'').startsWith(this.typeparam))
			var filetype=this.type_map[params.shift().substr(this.typeparam.length).toLowerCase()];
		if ((params[0]||'').startsWith(this.limitparam))
			var limit=params.shift().substr(this.limitparam.length);
		var q=((params[0]||'')==this.quietparam);   if (q) params.shift();
		var o=((params[0]||'')==this.replaceparam); if (o) params.shift();
		var m=((params[0]||'')==this.mergeparam);   if (m) params.shift();
		var a=((params[0]||'')==this.openparam);    if (a) params.shift();
		var btn=createTiddlyButton(place,label||this.label,prompt||this.prompt,
			function(){ config.macros.saveAs.go( this.getAttribute('target'),
				this.getAttribute('filename'), this.getAttribute('filetype'),
				this.getAttribute('filter'), this.getAttribute('limit'),
				this.getAttribute('quiet')=='true',
				this.getAttribute('overwrite')=='true',
				this.getAttribute('merge')=='true',
				this.getAttribute('autoopen')=='true',
				this);
				return false;
			});
		if (target) btn.setAttribute('target',target);
		if (filename) btn.setAttribute('filename',filename);
		btn.setAttribute('filetype',filetype||this.type_TW);
		btn.setAttribute('filter',params.join(' '));
		btn.setAttribute('limit',limit||0);
		btn.setAttribute('quiet',q?'true':'false');
		btn.setAttribute('overwrite',o?'true':'false');
		btn.setAttribute('merge',m?'true':'false');
		btn.setAttribute('autoopen',a?'true':'false');
	},
	go: function(target,filename,filetype,filter,limit,quiet,overwrite,merge,autoopen,here) {
		var cm=config.messages; // abbreviation
		var cms=config.macros.saveAs; // abbreviation
		if (window.location.protocol!='file:') // make sure we are local
			{ displayMessage(cm.notFileUrlError); return; }

		// get tidders, confirm filtered results
		var tids=cms.selectTiddlers(filter,here);
		if (tids===false) return; // cancelled by user
		if (cms.filter!=cms.emptyParam && cms.filter.length && !quiet)
			if (!confirm(cms.confirmmsg.format([tids.length,cms.filter]))) return;

		// get target path/filename
		if (!filetype) filetype=this.type_TW;
		target=target||cms.getTarget(filename,filetype==this.type_TX?'txt':filetype==this.type_CS?'csv':'html');
		if (!target) return; // cancelled by user

		var link='file:///'+target.replace(/\\/g,'/');
		var samefile=link==decodeURIComponent(window.location.href);
		var p=getLocalPath(document.location.href);
		if (samefile) {
			if (config.options.chkSaveBackups)
				{ var t=loadOriginal(p);if(t)saveBackup(p,t); }
			if (config.options.chkGenerateAnRssFeed && saveRss instanceof Function)
				saveRss(p);
		}
		var notes='';
		var total={val:0};
		var out=this.assembleFile(target,filetype,tids,limit||0,notes,quiet,overwrite,merge,total);
		var ok=saveFile(target,out);
		if (ok && autoopen) {
			if (!samefile) window.open(link).focus();
			else { store.setDirty(false); window.location.reload(); }
		}
		if (!quiet || !(ok && autoopen))
			displayMessage((ok?this.okmsg:this.failmsg).format([total.val,target]),link);
	},
	selectTiddlers: function(filter,here) {
		var cms=config.macros.saveAs; // abbreviation
		var tids=[]; cms.filter=filter||'';
		if (filter==cms.emptyParam)
			return tids;
		if (filter==config.macros.saveAs.hereParam) {
			var here=story.findContainingTiddler(here);
			if (here) var tid=here.getAttribute('tiddler');
			else var tid=prompt(config.macros.saveAs.hereMsg,'');
			while (tid && !store.tiddlerExists(tid)) {
				var err='"'+tid+'" not found.\nPlease try again.\n\n';
				var tid=prompt(err+config.macros.saveAs.hereMsg,tid);
			}
			if (!tid) return false;  // cancelled by user
			return [store.getTiddler(tid)];
		}
		if (filter==config.macros.saveAs.askParam) {
			filter=prompt(config.macros.saveAs.askMsg,'');
			if (!filter) return false;  // cancelled by user
			cms.filter=filter=='*'?'':filter;
		}
		if (!filter||!filter.length||filter=='*') tids=store.getTiddlers('title');
		else tids=store.filterTiddlers('[tag['+filter+']]');
		return tids;
	},
	getTarget: function(defName,defExt) {
		var cms=config.macros.saveAs; // abbreviation
		// get new target path/filename
		var newPath=getLocalPath(window.location.href);
		var slashpos=newPath.lastIndexOf('/'); if (slashpos==-1) slashpos=newPath.lastIndexOf('\\'); 
		if (slashpos!=-1) newPath=newPath.substr(0,slashpos+1); // trim filename
		if (!defName||!defName.length) { // use current filename as default
			var p=getLocalPath(window.location.href);
			var s=p.lastIndexOf('/'); if (s==-1) s=p.lastIndexOf('\\'); 
			if (s!=-1) defName=p.substr(s+1);
		}
		var defFilename=(defName||cms.defaultFilename).replace(/.html$/,'.'+defExt);
		var target=cms.askForFilename(cms.filePrompt,newPath,defFilename,defExt);
		if (!target) return; // cancelled by user
		// if specified file does not include a path, assemble fully qualified path and filename
		var slashpos=target.lastIndexOf('/'); if (slashpos==-1) slashpos=target.lastIndexOf('\\');
		if (slashpos==-1) target=target+(defName||cms.defaultFilename).replace(/.html$/,'.'+defExt);
		return target;
	},
	askForFilename: function(msg,path,file,defExt) {
		if(window.Components) { // moz
			try {
				netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
				var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
				var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
				picker.init(window, msg, nsIFilePicker.modeSave);
				var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
				thispath.initWithPath(path);
				picker.displayDirectory=thispath;
				picker.defaultExtension=defExt||'html';
				picker.defaultString=file;
				picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
				if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.path;
			}
			catch(e) { alert('error during local file access: '+e.toString()) }
		}
		else { // IE
			try { // XP/Vista only
				var s = new ActiveXObject('UserAccounts.CommonDialog');
				s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|';
				s.FilterIndex=(defExt=='txt')?2:3; // default to HTML files;
				s.InitialDir=path;
				s.FileName=file;
				if (s.showOpen()) var result=s.FileName;
			}
			catch(e) { var result=prompt(msg,path+file); } // fallback for non-XP IE
		}
		return result;
	},
	plainTextHeader:
		 'Source:\n\t%0\n'
		+'Title:\n\t%1\n'
		+'Subtitle:\n\t%2\n'
		+'Created:\n\t%3 by %4\n'
		+'Application:\n\tTiddlyWiki %5 / %6 %7\n\n',
	plainTextTiddler:
		'- - - - - - - - - - - - - - -\n'
		+'|     title: %0\n'
		+'|   created: %1\n'
		+'|  modified: %2\n'
		+'| edited by: %3\n'
		+'|      tags: %4\n'
		+'- - - - - - - - - - - - - - -\n'
		+'%5\n',
	plainTextFooter:
		'',
	newsFeedHeader:
		 '<'+'?xml version="1.0"?'+'>\n'
		+'<rss version="2.0">\n'
		+'<channel>\n'
		+'<title>%1</title>\n'
		+'<link>%0</link>\n'
		+'<description>%2</description>\n'
		+'<language>en-us</language>\n'
		+'<copyright>Copyright '+(new Date().getFullYear())+' %4</copyright>\n'
		+'<pubDate>%3</pubDate>\n'
		+'<lastBuildDate>%3</lastBuildDate>\n'
		+'<docs>http://blogs.law.harvard.edu/tech/rss</docs>\n'
		+'<generator>TiddlyWiki %5 / %6 %7</generator>\n',
	newsFeedTiddler:
		'\n%0\n',
	newsFeedFooter:
		'</channel></rss>',
	pureStoreHeader:
		 '<html><body>'
		+'<style type="text/css">'
		+'	#storeArea {display:block;margin:1em;}'
		+'	#storeArea div {padding:0.5em;margin:1em;border:2px solid black;height:10em;overflow:auto;}'
		+'	#pureStoreHeading {width:100%;text-align:left;background-color:#eeeeee;padding:1em;}'
		+'</style>'
		+'<div id="pureStoreHeading">'
		+'	TiddlyWiki "PureStore" export file<br>'
		+'	Source'+': <b>%0</b><br>'
		+'	Title: <b>%1</b><br>'
		+'	Subtitle: <b>%2</b><br>'
		+'	Created: <b>%3</b> by <b>%4</b><br>'
		+'	TiddlyWiki %5 / %6 %7<br>'
		+'	Notes:<hr><pre>%8</pre>'
		+'</div>'
		+'<div id="storeArea">',
	pureStoreTiddler:
		'%0\n%1',
	pureStoreFooter:
		'</div><!--POST-BODY-START-->\n<!--POST-BODY-END--></body></html>',
	assembleFile: function(target,filetype,tids,limit,notes,quiet,overwrite,merge,total) {
		var revised='';
		var now = new Date().toLocaleString();
		var src=convertUnicodeToUTF8(document.location.href);
		var title = convertUnicodeToUTF8(wikifyPlain('SiteTitle').htmlEncode());
		var subtitle = convertUnicodeToUTF8(wikifyPlain('SiteSubtitle').htmlEncode());
		var user = convertUnicodeToUTF8(config.options.txtUserName.htmlEncode());
		var twver = version.major+'.'+version.minor+'.'+version.revision;
		var v=version.extensions.SaveAsPlugin; var pver = v.major+'.'+v.minor+'.'+v.revision;
		var headerargs=[src,title,subtitle,now,user,twver,'SaveAsPlugin',pver,notes];
		switch (filetype) {
			case this.type_TX: // plain text
				var header=this.plainTextHeader.format(headerargs);
				var footer=this.plainTextFooter;
				break;
			case this.type_CS: // comma-separated
				var fields={};
				for (var i=0; i<tids.length; i++) for (var f in tids[i].fields) fields[f]=f;
				var names=['title','created','modified','modifier','tags','text'];
				for (var f in fields) names.push(f);
				var header=names.join(',')+'\n';
				var footer='';
				break;
			case this.type_NF: // news feed (XML)
				headerargs[0]=store.getTiddlerText('SiteUrl','');
				var header=this.newsFeedHeader.format(headerargs);
				var footer=this.newsFeedFooter;
				tids=store.sortTiddlers(tids,'-modified');
				break;
			case this.type_PS: // PureStore (no code)
				var header=this.pureStoreHeader.format(headerargs);
				var footer=this.pureStoreFooter;
				break;
			case this.type_TW: // full TiddlyWiki
			default:
				var currPath=getLocalPath(window.location.href);
				var original=loadFile(currPath);
				if (!original) { alert(config.messages.cantSaveError); return; }
				var posDiv = locateStoreArea(original);
				if (!posDiv) { alert(config.messages.invalidFileError.format([currPath])); return; }
				var header = original.substr(0,posDiv[0]+startSaveArea.length)+'\n';
				var footer = '\n'+original.substr(posDiv[1]);
				break;
		}
		if (parseInt(limit)!=0) tids=tids.slice(0,limit);
		var out=this.getData(target,filetype,tids,quiet,overwrite,merge,fields);
		var revised = header+convertUnicodeToUTF8(out.join('\n'))+footer;
		// if full TW, insert page title and language attr, and reset MARKUP blocks as needed...
		if (filetype==this.type_TW) {
			var newSiteTitle=convertUnicodeToUTF8(getPageTitle()).htmlEncode();
			revised=revised.replaceChunk('<title'+'>','</title'+'>',' ' + newSiteTitle + ' ');
			revised=updateLanguageAttribute(revised);
			var titles=[]; for (var i=0; i<tids.length; i++) titles.push(tids[i].title);
			revised=updateMarkupBlock(revised,'PRE-HEAD',
				titles.contains('MarkupPreHead')? 'MarkupPreHead' :null);
			revised=updateMarkupBlock(revised,'POST-HEAD',
				titles.contains('MarkupPostHead')?'MarkupPostHead':null);
			revised=updateMarkupBlock(revised,'PRE-BODY',
				titles.contains('MarkupPreBody')? 'MarkupPreBody' :null);
			revised=updateMarkupBlock(revised,'POST-SCRIPT',
				titles.contains('MarkupPostBody')?'MarkupPostBody':null);
		}
		total.val=out.length;
		return revised;
	},
	getData: function(target,filetype,tids,quiet,overwrite,merge,fields) {
		// output selected tiddlers and gather list of titles (for use with merge)
		var out=[]; var titles=[];
		var url=store.getTiddlerText('SiteUrl','');
		for (var i=0; i<tids.length; i++) {
			out.push(this.formatItem(store,filetype,tids[i],url,fields));
			titles.push(tids[i].title);
		}
		// if TW or PureStore format, ask to merge with existing tiddlers (if any)
		if (filetype==this.type_TW || filetype==this.type_PS) {
			if (overwrite) return out; // skip merge... forced overwrite
			var txt=loadFile(target);
			if (txt && txt.length) {
				var remoteStore=new TiddlyWiki();
				if (version.major+version.minor*.1+version.revision*.01<2.52) txt=convertUTF8ToUnicode(txt);
				if (remoteStore.importTiddlyWiki(txt) && (merge||confirm(this.mergeprompt.format([target])))) {
					var existing=remoteStore.getTiddlers('title');
					for (var i=0; i<existing.length; i++)
						if (!titles.contains(existing[i].title))
							out.push(this.formatItem(remoteStore,filetype,existing[i],url));
					if (!quiet) displayMessage(this.mergestatus.format([tids.length,out.length-tids.length]));
				}
			}
		}
		return out;
	},
	formatItem: function(s,f,t,u,fields) {
		if (f==this.type_TW)
			var r=s.getSaver().externalizeTiddler(s,t);
		if (f==this.type_PS)
			var r=this.pureStoreTiddler.format([t.title,s.getSaver().externalizeTiddler(s,t)]);
		if (f==this.type_NF)
			var r=this.newsFeedTiddler.format([t.saveToRss(u)]);
		if (f==this.type_TX)
			var r=this.plainTextTiddler.format([t.title, t.created.toLocaleString(), t.modified.toLocaleString(),
				t.modifier, String.encodeTiddlyLinkList(t.tags), t.text]);
		if (f==this.type_CS) {
			function toCSV(t) { return '"'+t.replace(/"/g,'""')+'"'; } // always encode CSV
			var out=[ toCSV(t.title), toCSV(t.created.toLocaleString()), toCSV(t.modified.toLocaleString()),
				toCSV(t.modifier), toCSV(String.encodeTiddlyLinkList(t.tags)), toCSV(t.text) ];
			for (var f in fields) out.push(toCSV(t.fields[f]||''));
			var r=out.join(',');
		}
		return r||'';
	}
};
//}}}
//{{{
// automatically add saveAs to backstage
config.tasks.saveAs = {
	text: 'saveAs',
	tooltip: config.macros.saveAs.prompt,
	action: function(){ clearMessage(); config.macros.saveAs.go(); }
}
config.backstageTasks.splice(config.backstageTasks.indexOf('save')+1,0,'saveAs');
//}}}
|Name|SaveAsPluginInfo|
|Source|http://www.TiddlyTools.com/#SaveAsPlugin|
|Documentation|http://www.TiddlyTools.com/#SaveAsPluginInfo|
|Version|2.7.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|documentation|
|Description|Documentation for SaveAsPlugin|
>//Note: This plugin replaces ''[[NewDocumentPlugin]]'' (except for the HTML+CSS "snapshot" features, which are now provided by [[SnapshotPlugin]].//
!!!!!Usage
<<<
This plugin automatically adds a 'save as' command to the TiddlyWiki 'backstage' menu so you can quickly create an exact copy of the current TiddlyWiki document.  The plugin also defines a macro that you can use to place a "save as..." command link into your sidebar/mainmenu/any tiddler (or wherever you like).  When the command link is clicked, a system-specific dialog box will be displayed so you can select/enter the desired target path and filename.
{{{
<<saveAs "label:..." "prompt:..." "filename:..." "type:..." "limit:..."
	quiet replace merge open filter|ask|here|none>>
}}}
//(all parameters are optional)//
*''label:...''<br>custom link text (instead of "save as...")
*''prompt:...''<br>custom tooltip text
*''filename:...''<br>default filename to be shown when asking for an output path/file
*''type:...''<br>a keyword, indicating one of the following output file formats:
**''~TiddlyWiki''<br>(or ''wiki'' or ''tw'') a TiddlyWiki HTML document 
**''~PureStore''<br>(or ''store'' or ''ps'') a TiddlyWiki "PureStore" HTML export file (just tiddlers, no core code)
**''~PlainText''<br>(or ''text'' or ''tx'') a plain text file listing of tiddler //source// content
**''Comma''<br>(or ''csv'' or ''cs'') a Comma-Separated Value (CSV) database/spreadsheet file
**''~NewsFeed''<br>(or ''xml'' or ''rss'' or ''nf'') an RSS ~NewsFeed XML file
*''limit:...''<br>output is limited to the specified number of tiddlers
*''quiet''<br>normally, when using filtering (see below), the number of matching tiddlers is reported and you are asked to confirm before saving those tiddlers to a new file.  Use the ''quiet'' keyword to suppress this confirmation step.
*''replace''<br>overwrites existing output file, if any, without confirmation.
*''merge''<br>merges with existing output file, if any, without confirmation.
*''open''<br>opens the newly created document file in a separate browser tab/window.
*''filter''<br>selects a subset of tiddlers to be written into the new document.  If omitted, all tiddlers in the document are written to the new file.  ''filter'' can either:
**a tag value<br>selects only tiddlers that are tagged with that value.  To select tiddlers based on a combination of tags, you can install [[MatchTagsPlugin]] to construct complex 'tag expressions' using full 'boolean' logic with AND, OR, and NOT operators, as well as nested parentheses.
**''ask''<br>prompts you to enter a tag (or tag expression) whenever you click on the 'save as...' command
**''here''<br>selects the current tiddler (if any).  If the command is not embedded within a tiddler, you will be prompted to enter a tiddler title.
**''none''<br>omits all tiddlers and creates a new //empty// document
<<<
!!!!!Examples
<<<
save all tiddlers:
>{{{<<saveAs>>}}}<br>try it: <<saveAs>>
save only tiddlers matching a single tag:
>{{{<<saveAs "label:create Import/Export starter" "filename:TW+ImportExport.html" ImportExportPackage>>}}}
>try it: <<saveAs "label:create Import/Export starter" "filename:TW+ImportExport.html" "ImportExportPackage>>
save to a ~PureStore format:
>{{{<<saveAs "label:create Import/Export archive" "filename:ImportExportPackage.html" type:PureStore open ImportExportPackage>>}}}
>try it: <<saveAs "label:create Import/Export archive" "filename:ImportExportPackage.html" type:PureStore open ImportExportPackage>>
save to a ~PlainText format:
>{{{<<saveAs "label:create Import/Export source listing" type:PlainText open ImportExportPackage>>}}}
>try it: <<saveAs "label:create Import/Export source listing" type:PlainText open ImportExportPackage>>
save tiddlers matching a complex combination of tags (requires [[MatchTagsPlugin]]):
>{{{<<saveAs (alpha or settings) and not systemConfig>>}}}
>try it: <<saveAs (alpha or settings) and not systemConfig>>
prompt for tag or tag expression each time:
>{{{<<saveAs "label:custom save as..." ask>>}}}
>try it: <<saveAs "label:custom save as..." ask>>
save this tiddler:
>{{{<<saveAs "label:save this tiddler..." {{"filename:"+tiddler.title}} quiet here>>}}}
>try it: <<saveAs "label:save this tiddler..." {{"filename:"+tiddler.title}} quiet here>>
<<<
!!!!!Revisions
<<<
2009.10.13 2.7.0 added 'here' param (saves current tiddler)
2009.08.16 2.6.2 fixed handling for backstage
2009.08.04 2.6.1 fixed handling when limit is omitted.
2009.08.03 2.6.0 added 'limit:nn' parameter.
2009.08.02 2.5.3 in getData(), if type=RSS, sort by modified (most recent first).
2009.07.03 2.5.2 TW252 fixup: don't call convertUTF8ToUnicode() for local loadFile() I/O
2009.04.30 2.5.1 custom fields in CSV output.
2009.04.19 2.5.0 added CSV format.
2008.09.29 2.4.3 in getData(), convert UTF8 to Unicode before merging (fixes international characters).
2008.09.28 2.4.2 in go(), fixed typo that prevented backstage SaveAs from working.
2008.09.24 2.4.1 if rewriting *current* file and chkSaveBackups and/or chkGenerateAnRssFeed is enabled, then write a backup file or RSS feed, respectively
2008.09.24 2.4.0 when 'open' param is used and file is saved to current location, reload() page instead of opening a new tab/window.  Added 'filename' param to specify default filename.  Added 'replace' and 'merge' keyword params to control file handling without asking user.  Improved use of 'quiet' flag to eliminate more unwanted messages
2008.09.19 2.3.2 fixed backstage SaveAs command (was defaulting to empty document).  in formatItem(), removed unnecessary convertUnicodeToUTF8() (was causing double-conversion!)
2008.09.16 2.3.1 fixed IE 'navigate away' error by returning false from button onclick handler
2008.09.11 2.3.0 added support for alternative file formats: ~PlainText (TX), ~PureStore (PS), or ~NewsFeed (XML) in addition to existing ~TiddlyWiki (TW) document format
2008.09.06 2.2.1 corrected handling of autoopen attribute so it only applies when "open" param is specified
2008.08.01 2.2.0 added "open" param to auto-open newly saved document
2008.07.20 2.1.3 added "quiet" param to bypass confirmation when using tag filter
2008.04.22 2.1.2 corrected use of getTarget() to check for "user cancelled"
2008.04.22 2.1.1 documentation fixes
2008.04.22 2.1.0 added support for tag filtering to completely replace [[NewDocumentPlugin]] (now retired)
2008.04.12 2.0.1 automatically add "saveAs" to backstage commands
2008.04.12 2.0.0 initial release based on [[NewDocumentPlugin]]

__Previous revisions from [[NewDocumentPlugin]]__
2008.04.20 1.8.0 added support for 'noCSS' and 'viewer' params for alternative snapshot output
2008.01.08 [*.*.*] plugin size reduction: documentation moved to ...Info tiddler
2007.12.04 [*.*.*] update for ~TW2.3.0: replaced deprecated core functions, regexps, and macros
2007.03.30 1.7.0 added support for "print" param as alternative for "snap".  When "print" is used, the filename is ignored and ouput is directed to another browser tab/window, where the print dialog is then automatically triggered
2007.03.30 1.6.1 added support for "here" keyword for current tiddler elementID and "prompt:text" param for specifying tooltip text
2007.02.12 1.6.0 in onClickNewDocument(), reset HTML source 'markup'
2006.10.23 1.5.1 in onClickNewDocument(), get saved parameter value for snapID instead of using default "contentWrapper" (oops!)
2006.10.18 1.5.0 new optional param for 'snap'... specify alternative DOM element ID (default is still "contentWrapper")
2006.08.03 1.4.3 in promptForFilename(), for IE (~WinXP only), added handling for ~UserAccounts.~CommonDialog
2006.07.29 1.4.2 in onClickNewDocument(), okmsg display is now linked to newly created file
2006.07.24 1.4.1 in promptForFilename(), check for nsIFilePicker.returnCancel to allow nsIFilePicker.returnOK **OR** nsIFilePicker.returnReplace to be processed
2006.05.23 1.4.0 due to very poor performance, support for tag *expressions* has been removed, in favor of a simpler "containsAny()" scan for tags
2006.04.09 1.3.6 in onClickNewDocument, added call to convertUnicodeToUTF8() to better handle international characters
2006.03.15 1.3.5 added nsIFilePicker() handler for selecting filename in moz-based browsers.  IE and other non-moz browsers still use simple prompt() dialog
2006.03.15 1.3.0 added "label:text" param for custom link text.  added special "all" filter parameter for "save as..." handling (writes all tiddlers to output file)
2006.03.09 1.2.0 added special "snap" filter parameter to generate and write "snapshot" files containing static HTML+CSS for currently rendered document
2006.02.24 1.1.2 Fix incompatiblity with TW 2.0.5 by removing custom definition of getLocalPath() (which is now part of TW core)
2006.02.03 1.1.1 concatentate 'extra' params so that tag expressions don't have to be quoted.   moved all text to 'formatted' string definitions for easier translation.
2006.02.03 1.1.0 added support for tag EXPRESSIONS.  plus improved documentation and code cleanup
2006.02.03 1.0.0 Created
<<<
/***
|Name|SaveFromWebConfig|
|Source|http://www.TiddlyTools.com/#SaveFromWebConfig|
|Documentation|http://www.TiddlyTools.com/#SaveFromWebPluginInfo|
|Version|1.3.1|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|configuration settings for SaveFromWebPlugin|
***/
/***
!!!!! URL for server-side 'reflector' script.
***/
//{{{
config.options.txtSaveFromWebScriptURL="savefromweb.php";
//}}}
/***
>Script can be hosted on ANY web server that supports PHP5.
***/
/***
!!!!! URL for TiddlyWiki core source
***/
//{{{
config.options.txtSaveFromWebSourceFile="http://www.TiddlyTools.com/empty.html";
//}}}
/***
>document URL for retrieving TiddlyWiki core source code. Using an *empty* TW minimizes data transfer for retrieving TW core. Can be on ANY domain... If blank, get core source code from current document URL.
***/
/***
!!!!! Target filename
***/
//{{{
config.options.txtSaveFromWebTargetFilename="";
//}}}
/***
>specifies the destination filename for the downloaded file. Can be any valid filename for local filesystem and appears as the default value when you are prompted to save the file.  If blank, the filename of the current document (or the domain name if there is no filename in the URL) is used.
***/
/***
!!!!! Pre-fetch option:
***/
//{{{
config.options.chkSaveFromWebPreFetch=false;
//}}}
/***
<<<
* true=get (and cache) TW core code when document is first loaded (i.e., when plugin is initialized)
* false=get and cache core code the first time the file is being saved
This option causes the plugin to retrieve the TiddlyWiki core source as soon as you load the document, instead of waiting for the first time you save.  This ensures that the TiddlyWiki core source can still be saved to the local filesystem even if your network connection is dropped before you save your changes.  Note that, even without pre-fetching, the core source is always cached after it is retrieved, so that subsequent saves don't do extra work to get it again.
<<<
***/
/***
!!!!! Local I/O option
***/
//{{{
config.options.chkSaveFromWebAttemptLocalIO=false;
//}}}
/***
<<<
(requires browser security permissions, i.e., "trusted site" settings).
The plugin will try to obtain security permission for direct filesystem I/O.  If you grant filesystem access to the script, then it writes the document directly to your filesystem, and doesn't use the server-side reflector script at all.  This allows you to save a remote file to your local filesystem, even if your net connection drops after you open the document. Note: if filesystem permissions are not granted, the plugin will automatically attempt to use the server-side reflector script as a fallback... even if no longer connected to the net.
<<<
***/
 
/***
|Name|SaveFromWebPlugin|
|Source|http://www.TiddlyTools.com/#SaveFromWebPlugin|
|Documentation|http://www.TiddlyTools.com/#SaveFromWebPluginInfo|
|Version|1.3.2|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|extend 'save changes' to get remote document contents and save to local filesystem |
Normally, when you are viewing a TiddlyWiki document over the web (i.e., not via {{{file://}}}) and you select the "save changes" (or "save to disk") command, an error message is displayed: //__"You need to save this TiddlyWiki to a file before you can save changes."__//  This plugin extends the use of {{{<<saveChanges>>}}} so that when you are viewing and/or editing a remote TiddlyWiki document, instead of receiving this somewhat confusing and unhelpful message, you can still click the "save changes" (or "save to disk") command to ''store a copy of the remote document directly onto your local filesystem'', //including any unsaved tiddler changes/additions you have made while working on-line.//
!!!!!Documentation
>see [[SaveFromWebPluginInfo]]
!!!!!Configuration
> see [[SaveFromWebConfig]]
!!!!!Revisions
<<<
2011.02.14 1.3.2 fix OSX error: use picker.file.path
2008.09.29 1.3.1 in saveFromWeb(), do NOT convert UTF8 to Unicode when merging retrieved source for submission to server-side reflector script.  Fixes mangling of international characters and symbols.
|please see [[SaveFromWebPluginInfo]] for additional revision details|
2007.06.26 1.0.0 initial release
<<<
!!!!!Code
***/
//{{{
version.extensions.SaveFromWebPlugin= {major: 1, minor: 3, revision: 2, date: new Date(2011,2,14)};
//}}}

//{{{
// DEFAULT SETTINGS
if (config.options.txtSaveFromWebScriptURL==undefined)
	config.options.txtSaveFromWebScriptURL="savefromweb.php";
if (config.options.txtSaveFromWebTargetFilename==undefined)
	config.options.txtSaveFromWebTargetFilename=""; // use current filename when blank
if (config.options.txtSaveFromWebSourceFile==undefined)
	config.options.txtSaveFromWebSourceFile=""; // use current URL when blank
if (config.options.chkSaveFromWebAttemptLocalIO==undefined)
	config.options.chkSaveFromWebAttemptLocalIO=true; // true=try to use local filesystem I/O (requires security permissions)
if (config.options.chkSaveFromWebPreFetch==undefined)
	config.options.chkSaveFromWebPreFetch=false; // true=retrieve TW core when document is first loaded
//}}}

//{{{
// OPTIONAL: get TW core source code when plugin is loaded (i.e., once per document session)
if (document.location.protocol!="file:" && config.options.chkSaveFromWebPreFetch) {
	// retrieve TW source from server...
	var src=document.location.href;
	if (config.options.txtSaveFromWebSourceFile && config.options.txtSaveFromWebSourceFile.length)
		src=config.options.txtSaveFromWebSourceFile;
	var target=config.options.txtSaveFromWebTargetFilename;
	if (!target.length) { // use current filename
		var loc=document.location.pathname;
		var slashpos=loc.lastIndexOf("/");
		target=(slashpos==-1)?loc:loc.substr(slashpos+1);
		if (!target.length) target=document.location.host+".html";
	}
	var xhr=loadRemoteFile(src,function(success,target,txt,src,xhr){if(success)config.saveFromWebSourceCache=txt;},target);
}
//}}}

//{{{
window.saveFromWeb_saveChanges = window.saveChanges;
window.saveChanges = function(onlyIfDirty,tiddlers) {
	// if on file:, just use standard core save handling
	if(document.location.protocol == "file:") { window.saveFromWeb_saveChanges.apply(this,arguments); return; }
	clearMessage();
	// get target filename
	var target=config.options.txtSaveFromWebTargetFilename;
	if (!target.length) { // use current filename
		var loc=document.location.pathname;
		var slashpos=loc.lastIndexOf("/");
		target=(slashpos==-1)?loc:loc.substr(slashpos+1);
		if (!target.length) target=document.location.host+".html";
	}
	// get TW core source location
	var src=document.location.href;
	if (config.options.txtSaveFromWebSourceFile && config.options.txtSaveFromWebSourceFile.length)
		src=config.options.txtSaveFromWebSourceFile;
	// if core source has already been cached, go straight to saving the file...
	if (config.saveFromWebSourceCache)
		{ window.saveFromWeb(true,target,config.saveFromWebSourceCache,src,null); return; }
	// otherwise, retrieve TW source from server...
	displayMessage("Retrieving TiddlyWiki core from "+src);
	var xhr=loadRemoteFile(src,window.saveFromWeb,target);
	if (!xhr) { // couldn't load remote, report core error message
		displayMessage("Could not retrieve TiddlyWiki core... download unsuccessful.");
		alert(config.messages.notFileUrlError);
		if(store.tiddlerExists(config.messages.saveInstructions))
			story.displayTiddler(null,config.messages.saveInstructions);
	}
	return;
}
//}}}

//{{{
window.saveFromWeb = function(success,target,txt,url,xhr) {
	if(!success) {
		displayMessage("Could not retrieve TiddlyWiki core... download unsuccessful.");
		alert(config.messages.cantSaveError);
		if(store.tiddlerExists(config.messages.saveInstructions))
			story.displayTiddler(null,config.messages.saveInstructions);
		return;
	}
	// Locate the storeArea div's in the original source
	var posDiv=locateStoreArea(txt);
	if(!posDiv) { alert(config.messages.invalidFileError.format([url])); return; }

	// cache the document source so subsequent saves don't have to retrieve the source each time
	if (!config.saveFromWebSourceCache) config.saveFromWebSourceCache=txt;

	// if we can get local filesystem access, then ask for a filename and merge/write the file
	if (config.options.chkSaveFromWebAttemptLocalIO) {
		try {
			// get local target path+filename (may be blocked by browser security)
			var target=promptForFilename( "Save file as:","C:\\",target,"html");
			if (!target || !target.length) return;
			saveBackup(target,txt);
			saveRss(target);
			saveEmpty(target,txt,posDiv);
			saveMain(target,txt,posDiv);
			return;
		} catch(e) { }
	}
	// otherwise, fallback to using online 'reflector' script (if any)
	if (config.options.txtSaveFromWebScriptURL.length) {
		displayMessage("Merging tiddlers with core and preparing for download...");
		var merged=txt.substr(0,posDiv[0]+startSaveArea.length)+"\n"+
			store.allTiddlersAsHtml()+"\n"+txt.substr(posDiv[1]);
		var title=getPageTitle().htmlEncode();
		merged=merged.replaceChunk("<title"+">","</title"+">"," "+title+" ");
		merged=updateLanguageAttribute(merged);
		merged=updateMarkupBlock(merged,"PRE-HEAD","MarkupPreHead");
		merged=updateMarkupBlock(merged,"POST-HEAD","MarkupPostHead");
		merged=updateMarkupBlock(merged,"PRE-BODY","MarkupPreBody");
		merged=updateMarkupBlock(merged,"POST-SCRIPT","MarkupPostBody");
		// create form in a hidden frame and submit it to server
		var html='<input type="hidden" name="filename" value="">'
			+'<input type="hidden" name="contents" value="">';
		var form=window.createHiddenForm(config.options.txtSaveFromWebScriptURL,html);
		form.filename.value=target;
		form.contents.value=merged;
		form.submit();
	}
}
//}}}

//{{{
window.createHiddenForm=function(action,body) {
	var f=document.getElementById("saveFromWebFrame");
	if (f) document.body.removeChild(f);
	var f=createTiddlyElement(document.body,"iframe","saveFromWebFrame");
	f.style.width="0px"; f.style.height="0px"; f.style.border="0px";
	var d=f.document;
	if (f.contentDocument) d=f.contentDocument; // For NS6
	else if (f.contentWindow) d=f.contentWindow.document; // For IE5.5 and IE6
	d.open();
	d.writeln('<form target="_self" action="'+action+'" method="post" enctype="multipart/form-data">'+body+'</form>');
	d.close();
	return d.getElementsByTagName("form")[0];
}
//}}}

//{{{
// note: if blocked by browser security, this function will throw an error...
// the CALLING function should use "try{...} catch(e){...}" to handle the security errors
window.promptForFilename=function(msg,path,file,defext) {
	var result="";
	if(window.Components) { // moz
		netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
		var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
		var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
		picker.init(window, msg, nsIFilePicker.modeSave);
		picker.displayDirectory=null;
		picker.defaultExtension=defext;
		picker.defaultString=file;
		picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
		if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.path;
	}
	else { // IE (XP only)
		var s = new ActiveXObject('UserAccounts.CommonDialog');
		s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|';
		s.FilterIndex=1; // default to ALL files;
		s.InitialDir=path;
		s.FileName=file;
		if (s.showOpen()) var result=s.FileName;
	}
	return result;
}
//}}}
|Name|SaveFromWebPluginInfo|
|Source|http://www.TiddlyTools.com/#SaveFromWebPlugin|
|Documentation|http://www.TiddlyTools.com/#SaveFromWebPluginInfo|
|Version|1.3.1|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|documentation|
|Description|documentation for SaveFromWebPlugin|
Normally, when you are viewing a TiddlyWiki document over the web (i.e., not via {{{file://}}}) and you select the "save changes" (or "save to disk") command, an error message is displayed: //__"You need to save this TiddlyWiki to a file before you can save changes."__//  This plugin extends the use of {{{<<saveChanges>>}}} so that when you are viewing and/or editing a remote TiddlyWiki document, instead of receiving this somewhat confusing and unhelpful message, you can still click the "save changes" (or "save to disk") command to ''store a copy of the remote document directly onto your local filesystem'', //including any unsaved tiddler changes/additions you have made while working on-line.//
!!!!!Usage
<<<
When you select <<saveChanges>> while viewing a remote document (i.e., a URL starting with http: rather than file:), the plugin first ''retrieves the TiddlyWiki core source code from the original document'' file stored on the remote server.  Then, it ''combines that core source with the tiddlers'' contained in the currently loaded document, ''including any changes you have made.''

While the next step //should// be to simply write the merged core+tiddler data directly to your hard drive, certain JavaScript features, such as reading/writing directly to the local filesystem, require expanded "cross-domain" privileges that are normally restricted for use only with ''signed'' scripts.  Although some browsers will let you grant filesystem permissions to a remotely-loaded script, this usually involves either a series of popup confirmation messages or manually re-configuring (and/or disabling) your browser's built-in security protections, which often include settings and options that most users find difficult to understand and inconvenient to access.

To avoid these security complications, the "save from web" processing requires just a few additional steps to prepare the modified document and deliver it to your browser: rather than writing the document data directly to the local filesystem, the plugin ''sends the merged core+tiddler data to a small companion script installed on the remote server'' (see savefromweb.php, below).  This simple "reflector" script then immediately ''downloads the new document data back to the browser'', which prompts you to either open the downloaded document for viewing or save it to your local hard drive.  Once the document has been stored on your filesystem, you can open that copy in your browser and work offline with full access to all TiddlyWiki features.

Important note for users of Internet Explorer's Popup Blocker feature...
>{{block{
//The default security settings of IE's "Popup Blocker" feature will warn you whenever an attempt is made to download a file in response to a scripted action such as the internal javascript processing performed by SaveFromWebPlugin.  However, if you then click IE's yellow warning message and select the 'download this file...' menu command, this will also cause IE to attempt a 'page transition' away from the currently loaded TiddlyWiki document... but, because there are unsaved changes in the document, you will first receive a confirmation message, allowing you to cancel the page transition.  Regrettably, this also prevents the download from succeeding.  Unfortunately, if you permit the page transition to occur, then your TiddlyWiki document is immediately reloaded and all the unsaved tiddler changes are discarded... and the download still fails to complete!//

''__To permit SaveFromWebPlugin to function properly with Internet Explorer, you will need to adjust the "download" security setting...__''
#From the ''Tools > Internet Options > Security'' tab,
#Select the "Internet" security zone (or what ever zone you are using to view the remote document)
#Press the "Custom level..." button
#In the "Settings" listbox, scroll to the "Downloads" section
#''ENABLE "automatic prompting for downloads"''(the first setting in the section)
#Press OK to accept the new settings.
}}}
<<<
!!!!!Configuration
> see [[SaveFromWebConfig]]
!!!!! Server script installation
<<<
On your web server, in the same directory as your published document, create a file called ''{{{savefromweb.php}}}'', containing the following PHP server-side script.
//{{{
<?php
// savefromweb.php
// Author: Eric L. Shulman / ELS Design Studios
// Source: http://www.TiddlyTools.com/savefromweb.php
// License: http://www.TiddlyTools.com/#LegalStatements
// Usage: install the php script on the server in the same directory as your TiddlyWiki document(s)

// This script 'reflects' any contents sent to it (via form POST) so they are
// sent back to the browser as a binary file.  This invokes the browser's built-in
// download-and-save handling, which does not require security permissions to access
// the local filesystem.

$args=$_POST;
header('Pragma: private');
header('Cache-control: private, must-revalidate');
header('Content-type: application/binary; charset="UTF-8"');
header('Content-disposition: attachment; filename="'.$args['filename'].'"');
$c=$args['contents'];
$c=str_replace("\\'","'",$c); // decode single-quotes
$c=str_replace("\\\"","\"",$c); // decode double-quotes
$c=str_replace("\\\\","\\",$c); // decode backslashes
$c=str_replace("\r\n","\n",$c); // change CRLF to LF
print $c;
?>
//}}}
<<<
!!!!!Direct filesystem access (browser security permissions)
<<<
Although sending the merged document data from browser to server and back again allows it to be saved to your filesystem without requiring you to extensively re-configure your browser's built-in security protections, it also increases the overall processing time because the document's data is actually being transmitted //three// times: it is first retrieved from the remote server to get the TiddlyWiki core source; then, after merging with the updated tiddler data, it is sent back to the server, which immediately 'reflects' it back to the browser for final handling by the built-in "file download" interface.

However, ''if you are accessing a "trusted site"'' (perhaps on a server within a secure private network), depending upon the specific options provided by your browser, ''you may be able to eliminate the round-trip processing by authorizing the appropriate filesystem security permissions in your browser''.  When filesystem access has been permitted, instead of making the round trip with the merged core+tiddler data, the plugin will directly prompt you for a destination path/file, using your computer's "native" path/file selection interface, and then write new the TiddlyWiki document data directly to the indicated location on your local file system.

FireFox users: please see [[FAQ_BrowserSecurity]] for information on configuring your browser to permit remote filesystem access from trusted sites
<<<
!!!!!Revisions
<<<
2008.09.29 1.3.1 in saveFromWeb(), do NOT convert UTF8 to Unicode when merging retrieved source for submission to server-side reflector script.  Fixes mangling of international characters and symbols.
2008.01.08 [*.*.*] plugin size reduction: documentation moved to SaveFromWebPluginInfo
2007.08.08 1.3.0 added caching of the downloaded TW core source code so it only has to be retrieved once.
2007.08.08 1.2.5 Added option to 'pre-fetch' the TW core so background download-and-cache is performed each time the document is loaded.  added option to try using direct filesystem access (with permissions) to bypass the round-trip through the server-side reflector script if the connection to the network is dropped after the document was loaded into the browser.  If permissions are not granted, fallback to use the server-side reflector script.
2007.08.07 1.2.0 removed 'download only' optimization.  The round-trip takes longer, but permits the reflector script to be located ANYWHERE on the net, at ANY valid URL, instead of having to use the same server location as the remote document.
2007.07.27 1.1.1 new documentation and code cleanup
2007.07.26 1.1.0 re-wrote to support savefromweb.php remote "reflector" script.  Allows use of browser's native download dialog to receive file as a fallback alternative to using local filesystem I/O (which would require additional security permissions)
2007.06.27 1.0.1 in saveFromWeb(), pass content from server through convertUnicodeToUTF8() before writing to file.
2007.06.26 1.0.0 initial release
<<<
/***
|Name|SaveTiddlerToFilePlugin|
|Source|http://www.TiddlyTools.com/#SaveTiddlerToFilePlugin|
|Documentation|http://www.TiddlyTools.com/#SaveTiddlerToFilePlugin|
|Version|1.1.1|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|toolbar command to save tiddler source definition to an external text file|
!!!!!Usage/Example
<<<
Embedded as a macro in tiddler content:
{{{
<<saveTiddlerToFile label:text prompt:text filename:text path:text>>
}}}
or, as a tiddler toolbar command in [[ViewTemplate]]:
{{{
<span class='toolbar' macro='toolbar saveTiddlerToFile'></span>
}}}
where:
* ''label'' //(optional)//<br>specifies the text to display for the command
* ''prompt'' //(optional)//<br>specifies the mouseover 'tooltip' text for the command
* ''filename'' //(optional)//<br>specifies the default filename to create.  You can use "*" within the filename as a 'substitution marker' that will be automatically replaced with the current tiddler's title.    If ''file'' is omitted, a system-specific 'ask for filename' dialog box will be displayed, using 'tiddlername.tid' as the suggested default filename.  //Note: if the tiddler is a plugin (tagged with <<tag systemConfig>>), then the suggested filename will use ".js" instead of ".tid"//
* ''path'' //(optional)//<br>specifies the default folder in which to create the output file.  If the path begins with "./" (or ".\" for Windows) it is treated as a relative path, and the path containing the current document will be prepended to create a full path reference.  Otherwise, the specified path must be a full path reference.  You can use "*" within the path as a 'substitution marker' that will be automatically replaced with the current tiddler's title.  If ''path'' is omitted, the path containing the current document will be used by default.
Examples:
>{{{<<saveTiddlerToFile>>}}}<br>Try it: <<saveTiddlerToFile>>
>{{{<<saveTiddlerToFile label:"save 'txt' file to current folder..." filename:*.txt>>}}}<br>Try it: <<saveTiddlerToFile label:"save 'txt' file to current folder..." filename:*.txt>>
>{{{<<saveTiddlerToFile label:"save 'txt' file to archive..." filename:*.txt path:./archive>>}}}<br>Try it: <<saveTiddlerToFile label:"save 'txt' file to archive..." filename:*.txt path:./archive>>
<<<
!!!!!Configuration
<<<
When {{{<span class='toolbar' macro='toolbar saveTiddlerToFile'></span>}}} is used to create a tiddler toolbar command, the default values for all parameters are applied (e.g., you will be prompted to select/enter a filename with a ".tid" or ".js" extension).  You can override these defaults by writing the following into a tiddler tagged with <<tag systemConfig>>:
{{{
config.commands.saveTiddlerToFile.filename="...";
config.commands.saveTiddlerToFile.path="...";
}}}
<<<
!!!!!Revisions
<<<
2011.02.14 1.1.1 fix OSX error: use picker.file.path
2008.04.22 1.1.0 converted from inline script to tiddler toolbar command
2007.06.26 1.0.0 initial release as inline script
<<<
!!!!!Code
***/
//{{{
version.extensions.SaveTiddlerToFilePlugin= {major: 1, minor: 1, revision: 1, date: new Date(2011,2,14)};

config.macros.saveTiddlerToFile = {
	label: "save this tiddler to a file",
	prompt: "save this tiddler's SOURCE text to a local file",
	askmsg: "select an output filename for this tiddler",
	okmsg: "Tiddler source written to %0",
	failmsg: "An error occurred while creating %0",
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		params = paramString.parseParams("anon",null,true,false,false);
		var label=getParam(params,"label",this.label);
		var prompt=getParam(params,"prompt",this.prompt);
		var filename=getParam(params,"filename","");
		var path=getParam(params,"path","");
		var btn=createTiddlyButton(place,label,prompt,
			function(event){config.macros.saveTiddlerToFile.go(this,event)});
		btn.setAttribute("filename",filename);
		btn.setAttribute("path",path);
	},
	go: function(src,event) {
		var cms=config.macros.saveTiddlerToFile; // abbreviation
		var here=story.findContainingTiddler(src); if (!here) return;
		var tid=here.getAttribute('tiddler');
		var filename=src.getAttribute("filename")||"";
		filename=filename.replace(/\*/g,tid);
		var path=src.getAttribute("path")||"";
		path=path.replace(/\*/g,tid);
		if (!path.length||path.substr(0,2)=="./"||path.substr(0,2)==".\\") {
			var curr=getLocalPath(document.location.href);
			var slashpos=curr.lastIndexOf("/"); if (slashpos==-1) slashpos=curr.lastIndexOf("\\"); 
			if (slashpos!=-1) curr=curr.substr(0,slashpos+1); // remove filename, leave trailing slash
			var trailingslash=curr.indexOf("\\")!=-1?"\\":"/"; // fixup for missing trailing slash
			if (path.length && path.substr(path.length,1)!=trailingslash) path+=trailingslash;
			path=!path.length?curr:curr+path.substr(2); // convert relative path to absolute path
		}
		var deffn=tid+(store.getTiddler(tid).isTagged("systemConfig")?".js":".tid");
		var target=filename.length?path+filename:cms.askForFilename(cms.askmsg,path,deffn); // ask
		if (!target||!target.length) return false; // cancelled by user
		var msg=saveFile(target,store.getTiddlerText(tid))?cms.okmsg:cms.failmsg;
		clearMessage(); displayMessage(msg.format([target]),"file:///"+target.replace(/\\/g,'/'));
	},
	askForFilename: function(msg,path,file) {
		if(window.Components) { // moz
			try {
				netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
				var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
				var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
				picker.init(window, msg, nsIFilePicker.modeSave);
				var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
				thispath.initWithPath(path);
				picker.displayDirectory=thispath;
				picker.defaultExtension='tid';
				picker.defaultString=file;
				picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
				if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.path;
			}
			catch(e) { alert('error during local file access: '+e.toString()) }
		}
		else { // IE
			try { // XP/Vista only
				var s = new ActiveXObject('UserAccounts.CommonDialog');
				s.Filter='All files|*.*|Text files|*.txt;*.tid;*.js|HTML files|*.htm;*.html|';
				s.FilterIndex=2; // default to TEXT files;
				s.InitialDir=path;
				s.FileName=file;
				if (s.showOpen()) var result=s.FileName;
			}
			catch(e) { var result=prompt(msg,path+file); } // fallback for non-XP IE
		}
		return result;
	}
}

// // toolbar definition
config.commands.saveTiddlerToFile= {
	text: "file",
	tooltip: config.macros.saveTiddlerToFile.prompt,
	filename: "",
	path: "",
	handler: function(event,src,title) {
		var ccs=config.commands.saveTiddlerToFile;
		if (ccs.filename.length) src.setAttribute("filename",ccs.filename);
		if (ccs.path.length) src.setAttribute("path",ccs.path);
		config.macros.saveTiddlerToFile.go(src,event);
		return false;
	}
};
//}}}
We are very aware that we are holding your precious data, so we ensure that your data is safe as far as we can:
!!SSL encryption
We use secure connections whenever you're viewing our site (look out for a little padlock in your browser) - this means that all ~URLs, data (e.g. your lists), pages, cookies are safely away from anyone eavesdropping on the connection between us. Any site that doesn't do this is bordering on breaking the data protection act (though not quite breaking it, yet). Our mail servers use SSL too, so the messages we send out are also safe from prying eyes while in transit, so long as the recipients mail servers support it too.
!!Make no attempt to hide
All our internet addresses resolve properly both forwards and backwards, and they change only very rarely (for example when we use a new ISP or add new servers) - we don't try to hide our servers because we don't send spam.
!!SPF support
All the mail we send in our own name uses our [[SPF]] record (which is set as strict as it can be) so recipients can be sure that we sent it, not someone pretending to be us.
!!Our locations
Since Edward Snowden's exposure of the ~US-based spy systems, the safe-harbour agreement that existed between the EU and US has essentially been nullified. The agreement requires that EU data on ~US-based hosts is not arbitrarily accessble by security services. This is something they can no longer guarantee, and it's likely they would be legally barred from revealing the fact if they know it is happening. Because of this, any data exported to the US from the EU must have explicit data subject consent, which is not something that is generally done, so it's therefore illegal to use ~US-based hosts for EU subscribers. We don't suffer from this problem because all our servers are are in the EU.
!!Our code
We follow best practices when it comes to writing secure code. We have defences against SQL and header injection attacks (about the most common kind) amongst others, and our servers are always up to date with the latest security patches.
Generally, we don't run other web apps (e.g. forums, blogs, ~CMSs etc) on our cluster (for example this wiki is not on it), so we're immune to security holes in such packages.
!!Viruses, trojans etc
We don't scan outgoing messages for two reasons. Firstly, we generate every message from scratch - we never forward anything from others, and we don't allow attachments or ~JavaScript in our messages, eliminating the most common infection vectors. Secondly, we don't run Windows (on desktops or servers), where 99% of security threats are found.
!!Backups
We take backups every night, so we guarantee to have a backup of all your data up to that age. Beyond that your data is your responsibility - you can download a copy from us any time you like.
!!Redundancy
Smartmessages runs on a cluster of redundant servers, allowing hot failover should one fail. All critical systems reside on RAID volumes and use redundant power supplies, so we're immune to the most common kinds of hardware failures.
!!Physical security
We host at ~RedBus in London's docklands, one of the UK's premier hosting locations, featuring clean, reliable power supplies, overspecified air conditioning, heavy physical security (retinal scanners etc).
!!But that's not the end of the story...
It's your data we're trying to keep safe, so you can help too. Implement [[SPF]], DKIM and DMARC for your own domain names and add us to your list of permitted email sources. Make use of our [[anti-phishing|Phishing]] features, especially if you are collecting data from your recipients through any of our services.

"Send to a friend" is a feature whereby having received a message from us, you feel you might like to forward it to a friend, and rather than using your email program's forward facility, you come to our site and fill in a form that sends your friend a copy of a message. Sounds like a good idea, but it has numerous hidden pitfalls.
!!Our solution
We provide a simple forwarding approach that has none of the downsides of the "traditional" approach: We provide a link that creates an email message in the user's usual email program, pre-populated with a link to the web version of the original message. This way:
* The message is sent directly from the recipient to the friend - we're not involved at all.
* It's instant and very easy to use - no logins, no web forms required
* The friend gets to see your message in full, with none of the original recipient's personalisation, and guaranteed free of any problems introduced by the original recipients own email service or program (many such programs corrupt or "constructively rewrite" forwarded messages).
!!What's wrong with the original way?
* It's very unlikely to be used. When reading an email you want to forward, your first reaction is to use your normal "forward" command, not one built in to the message.
* The person who receives the message effectively receives an unsolicited message from us. That's borderline legal in the UK, but in many countries it's an imprisonable offence (for example in Germany and Spain). Since it is us that is doing the sending, we're not too happy about that.
* For compliance with data protection laws (including in the UK), we have no rights to, and thus can hold no data about the friend that you sent the message to - we must discard everything, so this operation has zero value for collecting leads.
* Any unsolicited message is much more likely to be reported as spam, which when considered globally is likely to negatively impact the deliverability of ALL of your other mailings, including those for which you have full opt-in permission.
* If we pretend that you are the sender (e.g. by putting your address in the from field), we will be spotted as a fake, your message may well be rejected by any receiver that implements SPF (as we recommend), and you're even more likely to be reported as spam.
* In order to provide a web page where you could do this safely, you would need to log in to our site. If we didn't do this, we would be providing a spam gateway.
* It's not all down to you - we send message for all kinds of companies, and allowing it for one could reduce the deliverability of ALL those that use Smartmessages, including you.
* It's not permissible under Goodmail compliance, which is something we're aiming for (which gets better deliverability for everyone).
Does it still sound like a good idea? Even if we did provide this service, the one and only benefit you could possibly derive is a very small increase in the number of people that you know have viewed your message. Say 0.5% of recipients (being generous!) forward the message by this route. Perhaps half of those might arrive unaffected by filters (because of their forged origin), and of those that do, 75% of them might actually be read (the one upside of supposedly being from a friend!). All that effort represents a possible 0.18% increase in opening stats, something that could easily be wiped out by a single spam report.
!!How fast does sending go?
Rates vary because of many factors, but you should expect rates over 500,000 messages/hour for larger mailshots. We are working on improving this all the time. Factors affecting send rates include:
* Size and complexity of your template
* Size of your list
* Activities of other Smartmessages users
* Distribution of recipients (e.g. sending all to one ISP may be slower).
Lists of over about 16,000 recipients will get the highest send rates as over that size mailings can take best advantage of our sending cluster.
Should you have particular performance requirements beyond this, we can add servers to improve throughput at any time.
!!Why wouldn't I want it to go faster?
There is a large negative associated with a high send rate - it may adversely affect your delivery success rate. Many large ~ISPs (Yahoo!, Googlemail, AOL, MSN etc) impose restrictions on how many messages may be sent from one location in a given amount of time, or the frequency with which you send messages to specific users. They don't explicitly tell you that they are doing this, or let you know what their limits are, but you'll find that you'll get increasingly large numbers of bounces with a "4.4.1 Deferred Delivery" code. This code means that they may accept the message at some unspecified time in the future. Our mail server will retry delivery and progressively fall back with longer and longer times until it just gives up, typically after 4-7 days.
There are six steps to the send process: 
# Choose your template
# Name your mailshot & choose where it will be saved
# Add extra content if required for the email
# Choose who you are sending it to
# Choose which email address you are sending the email from & also the reply address
# Choose when to send the mailshot
There is also a 'final check' page where all the selected options are presented together for you to check before sending. Once a mailshot has been sent, it has gone, there is no 'undo', so be careful! Our servers go very fast, so hitting pause could still mean that 1000's of emails have been sent, and may not be possible at all on small mailings.

See also [[Continuous mailshots]].

Here are the screens from the system:

[IMG[images/smsend1.gif]]
[IMG[images/smsend2.gif]]
[IMG[images/smsend3.gif]]
[IMG[images/smsend4.gif]]
[IMG[images/smsend5.gif]]
[IMG[images/smsend6.gif]]
[IMG[images/smsend7.gif]]
[IMG[images/smsend8.gif]]
When you log in to Smartmessages, one of the first things you'll see is the setup checklist. These are various things you can set up in your account, and on external services, that will help improve how you appear to your subscribers, and help get your messages delivered to inboxes rather than spam filters. Here's a short description of each item we suggest:
!!Contact details
One sure way to improve your reception is for your subscribers know that messages are from you, and that they can get in touch with you easily. If you fill in your contact details on the settings page, we can make use of that information in many of the places that your subscribers come into contact with your messages and administrative pages (such as unsubscribes).
!!Set up your branding
It's great when your customers remember your name, but for many it's your branding - your colours, styles and logo - that brings more immediate familiarity. Set your colours and fonts on the branding page and you're well on your way to looking like the familiar face your customers know (and hopefully love!).
!!Upload your logo
Very much part of your branding, provide us with a nice logo and we'll use it when your subscribers interact with us,
!!Domain authentication
This is a techie one - your subscribers are far more likely to look favourably on messages from you if they can be sure that they are really from you and not someone pretending to be you. And it's not just your subscribers - the service providers your subscribers use also like such guarantees. If you're unfamiliar with DNS, you may need some help (from us or your local friendly geek) with setting up [[SPF]], [[DKIM|DKIM and DomainKeys]] and [[DMARC]].
!!Google analytics integration
[[Google analytics|GoogleAnalytics]] lets you know where your new leads are coming from. You're free to add your own URL parameters, but we can also do it for you autimatically, which is much less hassle.
!!Set up an anti-phishing page.
These days it's not enough to say who you are and that you're acting in good faith - you have to be able to back it up. That's why SPF and DKIM have become so important, but they only apply to the email side of things, and only come into effect //after// someone has signed up. To improve potential subscriber's confidence that you are really you (or more to the point, that we are really acting for you), you can use an [[anti-phishing page|Phishing]]. It's a simple concept - place a page on your own site pointing at us, tell us about it, and we will point at that page on our signup pages so that your new subscribers know we are really acting on your behalf.
!!Social network setup
You can't avid social networking these days - and it's become an important marketing channel in its own right. To help generate automatic links and sharing opportunities for the messages you send, tell us your facebook, twitter, ~LinkedIn and Google+ ~IDs on the settings page and we will use them to help promote you whenever we get the chance.
!!Import a mailing list	
This is  pretty important step - the best lists are home-grown, but if you've already got one and need to [[import it|UploadLists]], you'll need to massage it into the right format (we're pretty forgiving!) and upload it.
!!Send a mailshot
This is after all, why you are here! Just go to the mailings page and click a 'new mailshot' button.

Hopefully all that will get you off to a good start. Since you're reading this, you now know where our help site is. Do take a look around - there's an enormous amount of info about email marketing here. If you need more help, [[please ask|ContactUs]]!
Help and tips for your Smartmessages.net account
Smartmessages Help
http://wiki.smartmessages.net/
Launched in 2003 and quickly picked up business working with the Telegraph Group, we continue to provided emailing services for them today.

Our systems are based in the Docklands, London, UK 

Smartmessages is now a mature service having sent out hundreds of millions of emails for clients, including Ebookers and The Daily Mail.
Smartmessages Technical help wiki - If you have a technical question and the answer isn't here then please contact us and we will anser you and update our wiki.
/***
|Name|SnapshotPlugin|
|Source|http://www.TiddlyTools.com/#SnapshotPlugin|
|Documentation|http://www.TiddlyTools.com/#SnapshotPluginInfo|
|Version|1.4.3|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|save or print HTML+CSS image of rendered document content|
This plugin provides a macro as well as tiddler toolbar commands to create a file or browser window containing the //rendered// CSS-and-HTML that is currently being displayed for selected elements of the current document.
!!!!!Documentation
>see [[SnapshotPluginInfo]]
!!!!!Configuration
<<<
<<option chkSnapshotHTMLOnly>> output HTML only (omit CSS)
<<<
!!!!!Revisions
<<<
2011.02.14 1.4.3 fix OSX error: use picker.file.path
2011.01.03 1.4.2 added snapshotSaveViewer toolbar command
2010.12.15 1.4.1 added 'snapshot' class to wrapper
|please see [[SnapshotPluginInfo]] for additional revision details|
2008.04.21 1.0.0 initial release - derived from [[NewDocumentPlugin]] with many improvements...
<<<
!!!!!Code
***/
//{{{
version.extensions.SnapshotPlugin= {major: 1, minor: 4, revision: 3, date: new Date(2011,2,14)};

if (config.options.chkSnapshotHTMLOnly===undefined)
	config.options.chkSnapshotHTMLOnly=false;

config.macros.snapshot = {
	snapLabel: "save a snapshot",
	printLabel: "print a snapshot",
	snapPrompt: "save an HTML image",
	printPrompt: "print an HTML image",
	hereID: "here",
	viewerID: "viewer",
	storyID: "story",
	allID: "all",
	askID: "ask",
	askTiddlerID: "askTiddler",
	askDOMID: "askDOM",
	askMsg: "select an element...",
	hereItem: "tiddler: '%0'",
	viewerItem: "tiddler: '%0' (content only)",
	storyItem: "story column (one file)",
	storyFilesItem: "story column (multiple files)",
	allItem: "entire document",
	tiddlerItem: "select a tiddler...",
	IDItem: "select a DOM element by ID...",
	HTMLItem: "[%0] output HTML only (omit CSS)",
	fileMsg: "select or enter a target path/filename",
	defaultFilename: "snapshot.html",
	okmsg: "snapshot written to %0",
	failmsg: "An error occurred while creating %0",
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		var printing=params[0]&&params[0]=="print"; if (printing) params.shift();
		params = paramString.parseParams("anon",null,true,false,false);
		var id=getParam(params,"id","here");
		var label=getParam(params,"label",printing?this.printLabel:this.snapLabel);
		var prompt=getParam(params,"prompt",printing?this.printPrompt:this.snapPrompt);
		var btn=createTiddlyButton(place,label,prompt, function(ev){
			this.setAttribute("snapID",this.getAttribute("startID"));
			config.macros.snapshot.go(this,ev)
		});
		btn.setAttribute("startID",id);
		btn.setAttribute("snapID",id);
		btn.setAttribute("printing",printing?"true":"false");
		btn.setAttribute("HTMLOnly",config.options.chkSnapshotHTMLOnly?"true":"false");
	},
	go: function(here,ev) {
		var cms=config.macros.snapshot; // abbreviation
		var id=here.getAttribute("snapID");
		var printing=here.getAttribute("printing")=="true";
		var HTMLOnly=here.getAttribute("HTMLOnly")=="true";

		if (id==cms.askID||id==cms.askTiddlerID||id==cms.askDOMID) {
			cms.askForID(here,ev);
		} else if (id==cms.storyID) {
			story.forEachTiddler(function(t,e) {
				var out=cms.getsnap(e,e.id,printing,HTMLOnly);
				if (printing) cms.printsnap(out);
				else cms.savesnap(out,e.getAttribute('tiddler')+'.html');
			});
		} else {
			if (id==cms.allID) id="contentWrapper";
			var snapElem=document.getElementById(id);
			if (id==cms.hereID || id==cms.viewerID)
				var snapElem=story.findContainingTiddler(here);
			if (snapElem && hasClass(snapElem,"tiddler") && (id==cms.viewerID || HTMLOnly)) {
				// find viewer class element within tiddler element
				var nodes=snapElem.getElementsByTagName("*");
				for (var i=0; i<nodes.length; i++)
					if (hasClass(nodes[i],"viewer")) { snapElem=nodes[i]; break; }
			}
			if (!snapElem) // not in a tiddler or no viewer element or unknown ID
				{ e.cancelBubble=true; if(e.stopPropagation)e.stopPropagation(); return(false); }
			// write or print snapshot
			var out=cms.getsnap(snapElem,id,printing,HTMLOnly);
			if (printing) cms.printsnap(out); else cms.savesnap(out);
		}
		return false;
	},
	askForID: function(here,ev) {
		var ev = ev ? ev : window.event; 
		var cms=config.macros.snapshot; // abbreviation
		var id=here.getAttribute("snapID");
		var indent='\xa0\xa0\xa0\xa0';
		var p=Popup.create(here); if (!p) return false; p.className+=' sticky smallform';
		var s=createTiddlyElement(p,'select'); s.button=here;
		if (id==cms.askID) {
			s.options[s.length]=new Option(cms.askMsg,cms.askID);
			var tid=story.findContainingTiddler(here);
			if(tid) { 
				var title=tid.getAttribute("tiddler");
				if (here.getAttribute("HTMLOnly")!="true")
					s.options[s.length]=new Option(indent+cms.hereItem.format([title]),cms.hereID);
				s.options[s.length]=new Option(indent+cms.viewerItem.format([title]),cms.viewerID);
			}
			s.options[s.length]=new Option(indent+cms.tiddlerItem,cms.askTiddlerID);
			s.options[s.length]=new Option(indent+cms.IDItem,cms.askDOMID);
			s.options[s.length]=new Option(indent+cms.storyItem,"tiddlerDisplay");
			s.options[s.length]=new Option(indent+cms.storyFilesItem,cms.storyID);
			s.options[s.length]=new Option(indent+cms.allItem,"contentWrapper");
		}
		if (id==cms.askDOMID) {
			s.options[s.length]=new Option(cms.IDItem,cms.askDOMID);
			var elems=document.getElementsByTagName("*");
			var ids=[];
			for (var i=0;i<elems.length;i++)
				if (elems[i].id.length && elems[i].className!="animationContainer")
					ids.push(elems[i].id);
			ids.sort();
			for (var i=0;i<ids.length;i++) s.options[s.length]=new Option(indent+ids[i],ids[i]);
		}
		if (id==cms.askTiddlerID) {
			s.options[s.length]=new Option(cms.tiddlerItem,cms.askTiddlerID);
			var elems=document.getElementsByTagName("div");
			var ids=[];
			for (var i=0;i<elems.length;i++) { var id=elems[i].id;
				if (id.length && id.substr(0,story.idPrefix.length)==story.idPrefix && id!="tiddlerDisplay")
					ids.push(id);
			}
			ids.sort();
			for (var i=0;i<ids.length;i++) s.options[s.length]=new Option(indent+ids[i].substr(story.idPrefix.length),ids[i]);
		}
		s.options[s.length]=new Option(cms.HTMLItem.format([here.getAttribute("HTMLOnly")=="true"?"\u221a":"_"]),cms.HTMLItem);
		s.onchange=function(ev){
			var ev = ev ? ev : window.event; 
			var cms=config.macros.snapshot; // abbreviation
			var here=this.button;
			if (this.value==cms.HTMLItem) {
				config.options.chkSnapshotHTMLOnly=!config.options.chkSnapshotHTMLOnly;
				here.setAttribute("HTMLOnly",config.options.chkSnapshotHTMLOnly?"true":"false");
				config.macros.option.propagateOption("chkSnapshotHTMLOnly","checked",
					config.options.chkSnapshotHTMLOnly,"input");
			} else
				here.setAttribute("snapID",this.value);
			config.macros.snapshot.go(here,ev);
			return false;
		};
		Popup.show();
		ev.cancelBubble=true;
		if(ev.stopPropagation)ev.stopPropagation();
		return false;
	},
	getpath: function() {
		// get current path
		var path=getLocalPath(window.location.href);
		var slashpos=path.lastIndexOf("/");
		if (slashpos==-1) slashpos=path.lastIndexOf("\\"); 
		if (slashpos!=-1) path=path.substr(0,slashpos+1); // trim filename
		return path;
	},
	getsnap: function(snapElem,id,printing,HTMLOnly) {
		var cms=config.macros.snapshot; // abbreviation
		var out='<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />';
		if (printing)
			out+='<base href="file:///'+cms.getpath().replace(/\\/g,'/')+'"></base>\n';
		if (!HTMLOnly) {
			var styles=document.getElementsByTagName('style');
			var fmt='<style>\n/* stylesheet=%0 */\n%1\n\n</style>\n';
			for(var i=0; i < styles.length; i++)
				out+=fmt.format([styles[i].getAttribute('id'),styles[i].innerHTML]);
		}
		out+='</head>\n';

		var elems=snapElem.getElementsByTagName('input');
		for (var i=0; i<elems.length; i++) { var e=elems[i];
			if (e.type=='text')		e.defaultValue=e.value;
			if (e.type=='checkbox')	 	e.defaultChecked=e.checked;
			if (e.type=='radiobutton')	e.defaultChecked=e.checked;
		}
		var elems=snapElem.getElementsByTagName('textarea');
		for (var i=0; i<elems.length; i++)	elems[i].defaultValue=elems[i].value;

		var fmt='<body>\n\n<div class="snapshot %0">%1</div>\n\n</body>\n';
		out+=fmt.format([(id==cms.viewerID?'tiddler viewer':''),snapElem.innerHTML]);

		return '<html>\n'+out+'</html>';
	},
	printsnap: function(out) {
		var win=window.open("","_blank","");
		win.document.open();
		win.document.writeln(out);
		win.document.close();
		win.focus(); // bring to front
		win.print(); // trigger print dialog
	},
	savesnap: function(out,target) {
		var cms=config.macros.snapshot; // abbreviation
		// make sure we are local
		if (window.location.protocol!="file:")
			{ alert(config.messages.notFileUrlError); return; }
		var target=target||cms.askForFilename(cms.fileMsg,cms.getpath(),cms.defaultFilename);
		if (!target) return; // cancelled by user
		// if specified file does not include a path, assemble fully qualified path and filename
		var slashpos=target.lastIndexOf("/"); if (slashpos==-1) slashpos=target.lastIndexOf("\\");
		if (slashpos==-1) {
			var h=document.location.href;
			var cwd=getLocalPath(decodeURIComponent(h.substr(0,h.lastIndexOf('/')+1)));
			target=cwd+target;
		}
		var link="file:///"+target.replace(/\\/g,'/'); // link for message text
		var ok=saveFile(target,convertUnicodeToUTF8(out));
		var msg=ok?cms.okmsg.format([target]):cms.failmsg.format([target]);
		displayMessage(msg,link);
	},
	askForFilename: function(msg,path,file) {
		if(window.Components) { // moz
			try {
				netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
				var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
				var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
				picker.init(window, msg, nsIFilePicker.modeSave);
				var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
				thispath.initWithPath(path);
				picker.displayDirectory=thispath;
				picker.defaultExtension='html';
				picker.defaultString=file;
				picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
				if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.path;
			}
			catch(e) { alert('error during local file access: '+e.toString()) }
		}
		else { // IE
			try { // XP/Vista only
				var s = new ActiveXObject('UserAccounts.CommonDialog');
				s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|';
				s.FilterIndex=3; // default to HTML files;
				s.InitialDir=path;
				s.FileName=file;
				if (s.showOpen()) var result=s.FileName;
			}
			catch(e) { var result=prompt(msg,path+file); } // fallback for non-XP IE
		}
		return result;
	}
};
//}}}

// // TOOLBAR DEFINITIONS
//{{{
config.commands.snapshotSave = {
	text: "snap",
	tooltip: config.macros.snapshot.snapPrompt,
	handler: function(ev,src,title) {
		src.setAttribute("snapID","ask");
		src.setAttribute("printing","false");
		src.setAttribute("HTMLOnly",config.options.chkSnapshotHTMLOnly?"true":"false");
		config.macros.snapshot.go(src,ev);
		return false;
	}
};
config.commands.snapshotSaveViewer = {
	text: "snap",
	tooltip: config.macros.snapshot.snapPrompt,
	handler: function(ev,src,title) {
		src.setAttribute("snapID","viewer");
		src.setAttribute("printing","false");
		src.setAttribute("HTMLOnly",config.options.chkSnapshotHTMLOnly?"true":"false");
		config.macros.snapshot.go(src,ev);
		return false;
	}
};
config.commands.snapshotPrint = {
	text: "print",
	tooltip: config.macros.snapshot.printPrompt,
	handler: function(ev,src,title) {
		src.setAttribute("snapID","ask");
		src.setAttribute("printing","true");
		src.setAttribute("HTMLOnly",config.options.chkSnapshotHTMLOnly?"true":"false");
		config.macros.snapshot.go(src,ev);
		return false;
	}
};
config.commands.snapshotPrintViewer = {
	text: "print",
	tooltip: config.macros.snapshot.printPrompt,
	handler: function(ev,src,title) {
		src.setAttribute("snapID","viewer");
		src.setAttribute("printing","true");
		src.setAttribute("HTMLOnly",config.options.chkSnapshotHTMLOnly?"true":"false");
		config.macros.snapshot.go(src,ev);
		return false;
	}
};
//}}}

// // COPIED FROM [[StickyPopupPlugin]] TO ELIMINATE PLUGIN DEPENDENCY
//{{{
if (config.options.chkStickyPopups==undefined) config.options.chkStickyPopups=false;
Popup.stickyPopup_onDocumentClick = function(ev)
{
	// if click is in a sticky popup, ignore it so popup will remain visible
	var e = ev ? ev : window.event; var target = resolveTarget(e);
	var p=target; while (p) {
		if (hasClass(p,"popup") && (hasClass(p,"sticky")||config.options.chkStickyPopups)) break;
		else p=p.parentNode;
	}
	if (!p) // not in sticky popup (or sticky popups disabled)... use normal click handling
		Popup.onDocumentClick(ev);
	return true;
};
try{removeEvent(document,"click",Popup.onDocumentClick);}catch(e){};
try{addEvent(document,"click",Popup.stickyPopup_onDocumentClick);}catch(e){};
//}}}
Smartmessages has support for helping your message get out onto popular social networking sites including Facebook, Twitter, Google+ and ~LinkedIn.
!What can be shared?
Our social networking features are all about your subscribers liking the email they have been sent, rather than your Facebook/~LinkedIn etc pages. Because the email message itself can't be shared directly on the web, the sharing is of the web version of an email. This also prevents any personal data from being exposed. The template tags we offer all point at the web version, and because every web version has a unique URL we can report on them separately, which is the data that you'll see in our social networking reports.

[IMG[images/social.gif]]

If you want to point your subscribers at, for example, your facebook page, do that using a normal link, which will be tracked like any other link in your emails and reported with other clicks, not in the social networking reports.
!How social network sharing works
Here's an example of how facebook's 'like' button works; other networks are similar.
* A user sees something they like that has a Facebook 'like' button on it, which they click.
* Behind the scenes, Facebook receives the click, examines the source URL that the button gives it (which might not be the same page), requests it, and examines the page for additional information.
* A web page opens giving the recipient a chance to add a comment about the item, and also showing a preview of information about the link, which will usually include a title (which is a link), a short excerpt of text, and a preview thumbnail image.
* The recipients optionally fills in a comment, clicks ok.
* The 'like' appears in their timeline and is made visible to all their friends, who look at it and hopefully 'like' it too.
The 'behind the scenes' part here is implemented through something called [[open graph tags|http://ogp.me/]], which is a standard common to many social networking sites, including Facebook, ~LinkedIn and Google+. We take care of all that for you.
After this has happened, we can ask the social networks how many 'likes' have happened for the page in question, and report that figure back to you. The link we present to social networks is that of the un-personalised web preview of your mailshot, so there is no opportunity for leaking your recipients' confidential data.

If you want to target something other than the mailshot preview (for example your own site's home page), you'll need to create your own ~URLs, and you will be limited to using only static content (see notes about appearance below).
!Getting started
First things first: go to your Smartmessages account settings page and make sure the 'Allow open graph tags' option is checked. Open graph tags are added to your templates if they are going to be visible to social networks: they are used to extract information about the page (metadata) that's not available from the URL itself, such as the page title, a preview image for it, where it's from etc.

Next, fill in your Twitter ID - if you don't have one, sign up for one on [[twitter.com|https://twitter.com/]] - and we will use this when creating links to share on twitter, and mentions of your ID will automatically appear in your twitter client (or on the twitter web site).
!Twitter follow
If you want to have people follow you on twitter rather than share a link to one of your emails, fill in your twitter account in account settings and add the function tag {{{[[twitter_follow]]}}} to your template, and we'll generate a standard twitter follow button.
!Adding tags
You need to add links to your templates for the various sharing buttons. These are available in the pop-up menu of insertable fields in the HTML editor. The tags are {{{[[facebook_like]]}}}, {{{[[twitter_share]]}}}, {{{[[googleplus_share]]}}} and {{{[[linkedin_share]]}}}. That's all there is to it!
If you want to use your own graphics or text to build these links, we provide plain ~URLs for these services too. The tags for them are:
{{{[[$mailshot.facebook_staticshareurl]]}}}, {{{[[$mailshot.twitter_staticshareurl]]}}} and {{{[[$mailshot.linkedin_staticshareurl]]}}}.
!Practical considerations
The standard sharing buttons for social web sites are designed for use on the web, and are not a good fit for email, in particular they often use javascript and iframes, neither of which work //at all// in email. Fortunately there are simplified buttons for most of the networks where the most important sharing options are possible via nothing more than a normal HTML link. A complication is that you want to use the simplified buttons in email, but the enhanced ones when viewing a web page. This split functionality is built-in to the buttons we provide, but you'll find they don't quite look the same - the web versions often include additional dynamic information, such as the number of people that have already clicked, and the names of a recipient's friends that have clicked them, see below for examples.
In general we have selected the simplest, smallest display option available for each network, though we do include click counts if they are available as they are good encouragement. We have no control over how dynamic versions appear (and they may change at any time), so we suggest you place them in wrapper divs away from your main content, and don't get hung up on pixel perfection since we have no control over it!
!Button appearance
There are three different contexts that sharing buttons can appear in:
#When previewing a template in the template editor
#When previewing a mailshot (sent or unsent)
#In a sent email message
Cases 1 and 2 both occur in your web browser, so they make use of dynamic buttons. A problem with case 1 is that the URL that you 'share' is not visible to the outside world (it requires that the requester (in this case Facebook etc) has access to your Smartmessages account, which they don't); this means that the buttons may not work as expected in this view. Case 2 will work fine since the mailshot preview is publicly visible (it's the same thing as the web version your emails link to), and will display the dynamic versions of the buttons. Case 3 is how your recipients will typically see the buttons, which is the simplified versions mentioned above, which are small images with HTML links. Here are examples of how they appear in email:
* Facebook: [IMG[http://www.smartmessages.net/images/facebooklikebutton.gif]]
* Twitter: [IMG[http://www.smartmessages.net/images/twitterbutton.gif]]
* ~LinkedIn: [IMG[http://www.smartmessages.net/images/linkedinsharebutton.png]]
Where possible we have used official images for these buttons so they look the same as buttons for these services seen elsewhere, which unfortunately means they are sometimes not the same size or shape.

Please note that ''Google '+1' buttons will not appear in email'' because Google provides no static implementation of their button, so it can't work in email.

Here are the same buttons rendered in a web page:
[IMG[images/socialnetworkbuttons.png]]
as you can see, the dynamic versions often include additional features and thus look slightly different.
!Reporting
We request information about sharing activity from the social networks whenever we update your mailshot reports, and present it on our reporting pages. There may be some lag between shares happening and the stats being updated.
If a recipient receives a message they don't want, they often have two choices: unsubscribe from the list, or report the message as spam. All of our messages contain a working [[unsubscribe link|Unsubscribes]], and we also populate the "~List-Unsubscribe" header, making it as easy as possible for recipients to remove themselves from lists, however, some will still click the "report as spam" button. Most of the time this is not very helpful - it often won't stop them receiving messages because we don't get to find out about it (unlike unsubscribes), and it can adversely affect the delivery to those who do wish to receive them. Fortunately there is a little light at the end of the tunnel - some ~ISPs provide spam report data whenever someone reports a message as spam. Unfortunately, some don't do it at all, and some make it difficult to receive such messages. AOL is a shining example of how to do it right - not only do they provide the reports with a minimum of fuss, but they are in a format (Defined by an open standard - [[RFC 3462|http://www.faqs.org/rfcs/rfc3462.html]]) that's easy for us to process automatically. When other ~ISPs see the light, we will add them to our spam report handler.
Yahoo! requires that you use [[DKIM|DKIM and DomainKeys]] in order to receive spam reports.
!!What happens when we receive a spam report?
For the ~ISPs that we receive reports from, we process spam reports automatically, as soon as they are received. The address that the original message was sent to is unsubscribed from all lists within the account that the spam reported message was sent from, and a block is placed on that address so that no messages will ever be sent there again - a spam report has such bad consequences for deliverability that it's simply not worth risking them for the sake of a single address. The address will be suppressed from any subsequent list uploads and prevented from subscribing in future. Until the market recovers from the damage caused by the "never click an unsubscribe link" myth, we have little choice in this approach. 
!Welcome to Smartmessages Help
This is here to help you to use our service as easily as possible. Anything else you need help with? [[Ask us|ContactUs]]!
!New templates
There are important changes happening to Smartmessages templates - please read up on [[what's changing and how it might affect you|NewTemplates]].
!The parts of email marketing:
*[[What you send|Templates]]
*[[Who you send it to|MailingLists]]
*[[How & when you send it|SendingMailings]]
*Tracking [[opens|Open Tracking]] and [[clicks|Link Tracking]]
*[[What happened after you sent it|Reporting]]
*[[Mailing list|MailingLists]] management - [[unsubscribes|Unsubscribes]]/[[subscribes|Subscribing]], [[bounces|Bounces]] and [[spam notifications|SpamReports]]
Smartmessages does all these things for you.

Have a browse around this ~Wiki. Click topics on the right and they will appear in this middle area.
!!Technical Info
If you're looking for technical info to integrate Smartmessages services into your site, you're in the right place.
*[[Creating custom sign-up/subscription forms|Subscribing]]
*[[Creating custom unsubscribe forms|Unsubscribes]]
*[[Complete programmable API|API]] with [[sample code|APIExamples]]
*[[Callbacks for real-time integration|Callbacks]]
*[[Using Google Analytics|GoogleAnalytics]]
*Authenticating your email with [[SPF]] and [[DKIM|DKIM and DomainKeys]]
*Adding [[custom branding|Branding]] to standard Smartmessages pages
@font-face {
	font-family: 'Titillium Web';
	font-style: normal;
	font-weight: 400;
	src: local('Titillium Web'), local('TitilliumWeb-Regular'), url('//themes.googleusercontent.com/static/fonts/titilliumweb/v1/7XUFZ5tgS-tD6QamInJTcZ_o9VAbKgK36i-4snuAuCM.woff') format('woff');
}
@font-face {
	font-family: 'Titillium Web';
	font-style: normal;
	font-weight: 600;
	src: local('Titillium WebSemiBold'), local('TitilliumWeb-SemiBold'), url('//themes.googleusercontent.com/static/fonts/titilliumweb/v1/anMUvcNT0H1YN4FII8wprx3QmhlKDgQgT1VN2Ed1WFo.woff') format('woff');
}
@font-face {
	font-family: 'Titillium Web';
	font-style: normal;
	font-weight: 700;
	src: local('Titillium WebBold'), local('TitilliumWeb-Bold'), url('//themes.googleusercontent.com/static/fonts/titilliumweb/v1/anMUvcNT0H1YN4FII8wpr_5rZpfU3XI-FvUPGDRT0xY.woff') format('woff');
}
@font-face{
  font-family:'DINSchrift Alternate';
  src: url("font/dinengschrift-alternate-webfont.eot");
  src: url("font/dinengschrift-alternate-webfont.eot?#iefix") format("embedded-opentype"),
       url("font/dinengschrift-alternate-webfont.woff") format("woff"),
       url("font/dinengschrift-alternate-webfont.ttf") format("truetype"),
       url("font/dinengschrift-alternate-webfont.svg#dinschriftalternate") format("svg");
  font-weight:normal;
  font-style:normal
}

h1, h2, h3, h4, h5, h6, div.title {
    font-family: "DINSchrift Alternate", Arial, sans-serif;
    font-weight: 500;
    text-shadow: 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.1);
    text-rendering: optimizelegibility;
}
h1, .title {
    font-size: 35px;
    line-height: 37px;
    padding: 3px 6px;
    background-color: #F9DDA0;
    border-bottom: none;
}
.title {
    background-color: #F8CB66;
    margin: 0 0 6px;
    padding: 3px 6px;
}
h2 {
    font-size: 29.25px;
    line-height: 36px;
    border-bottom: 2px solid #F9DDA0;
}
h3 {
    font-size: 22.75px;
    line-height: 36px;
    border-bottom: 1px solid #F9DDA0;
}
h4 {
    font-size: 16.25px;
    line-height: 18px;
}
h5 {
    font-size: 13px;
    line-height: 18px;
}
h6 {
    font-size: 11.05px;
    line-height: 18px;
}
body, p, td, th, span, div, a, input, textarea {
    font-family: "Titillium Web","Trebuchet MS","Lucida Grande","Lucida Sans",Tahoma,sans-serif;
    font-size: 13px;
    line-height:18px;
}
strong {
    font-weight: 700;
}
p {
    margin: 0 0 9px;
}
.header {
    background-color: #333;
}
.viewer code, .viewer pre {
    font-size: 1.0em;
}
.viewer th {
    color: #333;
    background-color: #F9DDA0;
}
There are several ways that people can subscribe to your lists. In order of increasing complexity and flexibility:
* A simple signup link
* Our standard signup form
* A custom form using our processor
* A custom form using our [[API]]
On the contacts page you'll see a list of your mailing lists, and each one has a "Link" link which will take you to a subscribe page for that list, for example https://www.smartmessages.net/subscribe/1886. If you want to add a simple subscribe link to your own pages, you should use whichever of those links is appropriate. The default form captures just the email address and a single-field name. If you want more than that, you can build your own form.
!!Ready-to-roll signup forms
If you click on 'manage' on the contacts page to look at an individual mailing list, you'll see a tab in the actions section called 'Signup form'. In there you'll find a text box containing a ready-to-use HTML form for that list. Just select it, copy it and paste it into your own site's HTML.
!!Custom signup forms
Many of our customers want to use their own web forms to submit information to our subscription processor, and that's also easy to do - just point your form's action at https&#58;&#47;/www.smartmessages.net/subscribe.php. The standard ready-to-roll form we provide has lots more options available in its processor via the following parameters (bold fields are required):
*''//command//'' - the value 'subscribe'
*''//emailaddress//'' - the email address of the new subscriber
*''//mlid//'' - the ID of the mailing list they are subscribing to (this is the value that's visible in the "link" links on the contacts page)
*//title// - 'Mr', 'Miss', 'Dr.', 'Herr' etc.
*//dear// - a straightforward single-field name that actually corresponds with our 'dear' field
*//firstname// - the subscriber's first name
*//lastname// - the subscriber's last name
*//companyname// - the subscriber's company name
*//postcode// - the subscriber's postcode
*//language// - the subscriber's preferred language (2-character ISO 639-1 code, e.g. 'en', 'fr', 'de', defaults to 'en')
*//country// - the subscriber's country (2-character ISO 3166 code e.g. 'GB', 'FR', 'DE', defaults to 'GB')
*//redirect// - a URL to go to after processing the subscription request instead of displaying our own default page (don't forget to ~URL-encode it)
*//json// - any value - its presence will indicate that you want a response in a JSON format (e.g. you're subscribing from ~JavaScript)
By building your own form and providing a redirect URL, you can use our subscribe processor completely transparently and invisibly to your users. If you specify a redirect URL, you will receive an HTTP GET request to that URL containing your submitted parameters (so you can process it further - for example add it to your own contact database) along with additional information about the success (or not) of the subscription request in a 'statuscode' parameter. This has the following meanings:
*1 - Success
*2 - Already subscribed
*3 - Internal error
*4 - Missing required parameters
*5 - Invalid email address
*6 - Invalid mailing list ID
The JSON output also includes a 'success' value that is true for codes 1 and 2, false for others, along with a 'message' value containing a plain-text explanation of the response.
Here's a minimal example form that you can adapt:
{{{
<form action="https://www.smartmessages.net/subscribe.php" method="post">
  <p>
    <input type="hidden" name="command" value="subscribe" />
    <input type="hidden" name="mlid" value="<insert your mailing list ID here>" />
    Name: <input type="text" name="dear" />
    Email address: <input type="text" name="emailaddress" />
    <input type="submit" name="Subscribe" />
  </p>
</form>
}}}
Of course you can add more input fields from the list above.
If you want subscribers to be able to choose from multiple lists, create an HTML select input with multiple options for the different mailing lists' ~IDs you want.
!!Landing page
Every Smartmessages account gets to use our simple landing page which includes data capture of all standard fields and the option to subscribe to all mailing lists that are marked as visible. You'll find the link to your landing page on the front page after you log into Smartmessages.
!!API
If you are undertaking more complex integrations (for example capturing custom fields or a full postal address) then you will be better served by the subscribe and setUserInfo functions in our [[API]].
A suppression list is an "anti-mailing list" - a list of people that you do NOT want to mail.

We maintain several different kinds of suppression list:
*Unsubscribes
*Manual suppressions (for example people that phoned you up to ask to be unsubscribed)
*Spam reporters
*Persistent bouncers
[[Spam reporters|SpamReports]] are always applied account-wide, to all your mailing lists, so if someone reports one of your messages as spam, they will be removed from all your lists and prevented from being uploaded to all mailing lists in future. The small increase in delivery count you might get from persisting with such recipients is far outweighed by the negative impact it can have on your deliverability to everybody else. You can download a list of the people who have reported your messages as spam on the contact page.

Persistently [[bouncing|Bounces]] addresses are the only thing we share across all accounts, so you can benefit from some level of list cleaning simply by uploading your list before you have sent anything, just because one of our other customers may already have ascertained that some addresses bounce persistently. This bounce list is not available for download.

We do not currently have a mechanism for suppressing entire domains, but it's something we are considering.

When you do a list upload, all addresses that match any of these suppressions are removed before they are added to your list.
!!Removing suppressions
Sometimes you might want to undo or remove a suppression, for example if you unsubscribed yourself while testing. Each mailing list page has an 'add a single subscriber' option, and if you enter an email address here, any suppressions they have will be removed, and they will be subscribed to your list. We consider that if you have sufficient administrative privileges, then adding a single address is something we will allow. Similarly, anyone completing a double-opt-in subscription will have suppressions removed, not least because this requires explicit opt-in (which we consider a reasonable request to override any previous opt-out), and can't be done to a bouncing address. In short, suppressions are ONLY applied while uploading lists.
!!Global Unsubscribe
You'll see something called "Global unsubscribe" mentioned on our contacts page. This parameter controls what happens when someone unsubscribes, and it can be ON (the default) or OFF. This is a parameter we maintain control over, and you need to ask us if you'd like it changed.
!!!Global unsubscribe ON
If global unsubscribe is ''ON'', when someone unsubscribes from ''ONE'' of your lists, they will be removed from ''ALL'' of your lists. They are added to a suppression list that will prevent them from being uploaded into ''ANY'' list. This is the default mode of operation, and is equivalent to what most other email providers do too.
!!!Global unsubscribe OFF
If global unsubscribe is ''OFF'', when someone unsubscribes from ''ONE'' of your lists, they will be removed from ''ONLY'' that list. They will be added to a suppression list linked to only that list. Subscriptions of that address to other lists will be unaffected, and you will be able to upload them into other lists.

We maintain this policy because of the following scenario:
* Customer uploads their address list into Smartmessages.
* They send a few mailshots to it.
* Records of bounces, unsubscribes, spam reports and other usage data accumulates in Smartmessages.
* Customer uploads their original (or their own new version) address list into a new list on Smartmessages.
At this point with global unsubscribe OFF, sending to the new list would be illegal because it would contain people that had unsubscribed. For this reason, we will only turn off global unsubscribe for customers who can demonstrate that they are handling their data with sufficient care that they don't run into this problem.

Some of our customers want to integrate their mailings with surveys using services like [[SurveyMonkey|http://www.surveymonkey.com/]] and packages like [[LimeSurvey|http://www.limesurvey.org/]]. Here are some integration notes.
!!~SurveyMonkey
~SurveyMonkey doesn't preload lists of potential respondents, instead it relies on collecting details of those that do respond. This means that they can't prevent unauthorised people accessing the survey, and also that they can't offer any data pre-population or personalisation. On the upside, it also means there is very little setup required. To connect Smartmessages messages to a survey, you need to use their [[web collector|http://help.surveymonkey.com/articles/en_US/kb/How-do-I-send-my-survey-to-respondents-or-get-responses-to-my-survey]], and you'll generate ~URLs to feed into it using our template or mailshot editor. You need to start with a link to the anonymous survey entry point and add a unique identifier for each recipient - their email address will do nicely, though we need to ~URL-escape it as it is embedded in a URL. So a base URL for your survey (obtained through your ~SurveyMonkey account interface) that you link to from your message template might be:
{{{
http://www.surveymonkey.com/s.aspx?sm=v8MbvURxoHkWfvud7Or3Cg_3d_3d
}}}
You then need to add a 'c' paramater to it like this:
{{{
http://www.surveymonkey.com/s.aspx?sm=v8MbvURxoHkWfvud7Or3Cg_3d_3d&c=[[$subscriber.email|escape:'url']]
}}}
After your survey is complete you can download the results from ~SurveyMonkey.
For those that like to know such things, here is some technical background on Smartmessages.
* Built in OO PHP 5.4
* Message generation using [[PHPMailer|https://github.com/PHPMailer/PHPMailer]] (of which we are the maintainers)
* Tested with ~SimpleTest
* Templates (for both site and messages) with Smarty 3
* Percona Server ~MySQL 5.6 database cluster
* Memcached distributed cache
* Run on Ubuntu Linux, on dual- and quad-core servers
* Cluster deployment for scalability and redundancy (using linux-ha) - we can add more servers to increase speed as needed
* [[CKEditor|http://ckeditor.com]] WYSIWYG editor and [[CKFinder|http://cksource.com/ckfinder]] file selector
* Last and definitely not least, ~GreenArrow qmail-based mail servers, capable of > 2million messages/hour per server!
This describes all the tags, variables and functions you can use when building your templates. Before getting into this, you should be familiar with our [[basic template syntax|TemplateSyntax]].
!HTML and plain text
There are two parts to a template:
* ''HTML'' contains markup source that the mail client will render in a similar way to how a browser renders a web page.
* ''Plain text'' is used to generate a markup-free version that's used by simpler email clients, including some mobile clients (e.g. Blackberry, Pine, Mutt).
Email messages are constructed in a way that uses both HTML and plain parts, and tells receiving programs to prefer the HTML version, but fall back to the plain text one if it's not able to display it. It's important to include both versions of your content so that recipients can choose how to read it, and many spam filters frown upon ~HTML-only messages.
!Subjects are dynamic too
Subject lines added via the template page or while creating a mailshot are also fully dynamic - you can use all the same tags, variables and functions to personalise your subject lines, though be aware that HTML will not work in subject lines.
!Careful what you wish for!
Our template tags are free-form and you can put them anywhere you like, however that also means ''you are free to build things that make no sense and will not work'', for example you can say: {{{<a href="[[$subscriber.firstname]]">click me</a>}}} which is unlikely to be useful. It's particularly the case when fields are used in ~URLs; Make sure that your ~URLs do not contain spaces, accented characters or anything else that's invalid - ~URL-encoding is always the safe option, and you can apply that to any value using the {{{escape}}} modifier (please see the notes on using quotes with modifier parameters below), for example:
{{{
<a href="http://example.com/data.php?name=[[$subscriber.informal_greeting|default:"Subscriber"|escape:'url']]">Click me</a>
}}}
!!Test, test test!
To make sure what you produce is valid and workable as well as looking great, we strongly recommend that you make use of the preview, validation, link and image checking services Smartmessages provides, and send instant, 1-click test mailings to your test list.
!Variables
Smartmessages organises the various bits of data available to your templates into arrays of related properties. These are:
* {{{$system}}}: The Smartmessages system itself
* {{{$account}}}: Your Smartmessages account
* {{{$subscriber}}} The person a message is being sent to
* {{{$subscription}}}: The subscription of the recipient to the mailing list this mailing is being sent to
* {{{$mailshot}}}: This particular mailing
* {{{$campaign}}}: The folder that this mailshot is placed in
* {{{$mailinglist}}}: The mailing list this mailshot is being sent to
These are covered in detail below. To refer to any of these elements in your templates you need to create a tag containing the variable name followed by a dot, followed by the element name you want to use, for example {{{$account.name}}}. All content is automatically escaped so that it doesn't corrupt the surrounding HTML, so if your account name is {{{me < you}}} it will render to {{{me &lt; you}}} unless you specify the {{{nofilter}}} keyword.
Any element can be made to display a fallback/default value if it is empty by using the {{{default}}} modifier, see the modifiers section for how to use that.
!!{{{$system}}}
These elements provide useful information about the Smartmessages system itself, allowing you to construct ~URLs dynamically so that things don't break should things move around.
!!!{{{rooturl}}}
The base URL of the Smartmessages system. This is usually {{{https://www.smartmessages.net/}}}. Notice that it uses SSL.
!!!{{{rooturlnossl}}}
The same as {{{rooturl}}}, but using a non-secure http URL, so it's usually {{{http://www.smartmessages.net/}}}.
!!!{{{language}}}
A locale code of the language of this message. This is for future expansion, for now it will always be {{{en_gb}}}, indicating British English.
!!!{{{charset}}}
The character set this message uses. This used to be variable, but now will always be {{{utf-8}}}, indicating the ~UTF-8 encoding of the unicode character set.
!!!{{{library_rooturl}}}
This is the base URL of the image library Smartmessages provides. You probably don't need to use this!
!!!{{{ispreview}}}
This boolean value is true when this message is viewed as a web page or preview as opposed to as an email message. You can use this in conditions to display content sections specific to those contexts, for example:
{{{
[[if $system.ispreview]]
<p>You can sign up to receive messages like this <a href="[[$mailinglist.subscribe_url]]">here</a></p>
[[else]]
<p>Thanks for being a subscriber!</p>
[[/if]]
}}}
This would provide a signup link to anyone viewing the message as a web page rather than in their email.

!!{{{$account}}}
This holds information relating to your account as a whole, and is common to all messages in all mailshots you send. We make use of these elements in our standard templates to brand them for you.
!!!{{{name}}}
The name of your account!
!!!{{{path}}}
The name of the folder that contains your uploaded images.
!!!{{{address}}}
Your full address, as entered on the settings page. This includes line breaks, which of course are not shown in HTML output, but you can convert them to other sequences for better output using modifiers:
{{{
[[$account.address|nl2br nofilter]]
[[$account.address|stripbreaks:", "]]
}}}
The first example converts the breaks to HTML {{{<br>}}} tags; you must add the nofilter keyword to prevent the generated tags from being escaped. The second example uses the {{{stripbreaks}}} modifier, resulting in the address appearing in one line with a comma and space between each line in the original address.
!!!{{{companynumber}}}
It's a legal requirement to display your company number in all marketing communications in the UK, so we make it easy to get at using this tag.
!!!{{{url}}}
Your home page URL, as entered on the settings page.
!!!{{{email}}}
Your contact email address, as entered on the settings page.
!!!{{{country}}}
The name of your country, generated using standard [[ISO3166|http://www.iso.org/iso/country_codes.htm]] names in English.
!!!{{{countrycode}}}
Your [[ISO3166-1 alpha-2|http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2]] country code, for example {{{GB}}} for the United Kingdom.
!!!{{{phone}}}
your default phone number, as entered on the settings page.
!!!{{{useopengraphtags}}}
A boolean value indicating whether your account is set to use [[Facebook's open graph tags|https://developers.facebook.com/docs/web/share]]. This is used when generating web versions to make your pages look better when they are shared on social networks, and is on by default.
!!!{{{privacypolicy_url}}}
The URL of your privacy policy. You can set a custom policy in your account settings, but otherwise we provide one for you, and this URL will point at it.
!!!{{{subscriptionpolicy}}}
Except in some special cases, this will be the string "{{{double opt-in}}}".
!!!{{{landingpage_url}}}
The URL of your landing page, as linked from your contacts page. This is a central location where your subscribers can see and edit their own data (allowing you to fulfil the legally-required data access requests), and also manage their subscription to all your public mailing lists at once. This is a generic URL not specific to any particular subscriber - see {{{$subscriber.landingpage_url}}}.
!!!{{{imagebase_url}}}
The base URL of your uploaded images folder, including a trailing slash; related to {{{$account.path}}}.
!!!{{{logo}}}
If you have set a custom logo in your settings page, this generates a complete image tag to display it. If you have also set an account URL, it will be wrapped in a link to that URL.
!!!{{{haslogo}}}
A boolean value indicating whether you have uploaded a logo.
!!!{{{forecolour}}}
Your default foreground colour (as an HTML hex colour) as set on your branding page. Defaults to black ({{{#000000}}}). Also available as {{{forecolor}}} for our transatlantic brethren.
!!!{{{backcolour}}}
Your default background colour (as an HTML hex colour) as set on your branding page. Defaults to white ({{{#ffffff}}). Also available as {{{backcolor}}} for our transatlantic brethren.
!!!{{{bodyfonts}}}
The CSS body font stack you have selected on your branding page. Defaults to an iOS-style "{{{'Helvetica Neue',Helvetica,Arial,sans-serif}}}" stack. Generally you will always need to add the {{{nofilter}}} keyword to prevent quoted values from being ~HTML-escaped.
!!!{{{headingfonts}}}
The CSS heading font stack you have selected on your branding page. Defaults to an iOS-style "{{{'Helvetica Neue',Helvetica,Arial,sans-serif}}}" stack. Generally you will always need to add the {{{nofilter}}} keyword to prevent quoted values from being ~HTML-escaped.
!!!{{{twitter}}}
Your twitter ID, as entered on the settings page. This includes the leading {{{@}}} symbol.
!!!{{{twitter_pageurl}}}
The URL of your twitter home page.
!!!{{{twitter_follow}}}
This generates a javascript-based standard twitter follow button for web versions and previews, and a static link for email messages. See our social network function tags for more social networking tools.
!!!{{{linkedin}}}
Your ~LinkedIn ID, as entered on the settings page.
!!!{{{linkedin_pageurl}}}
The URL of your ~LinkedIn homepage.
!!!{{{googleplus}}}
Your Google Plus ID, as entered on the settings page.
!!!{{{googleplus_pageurl}}}
The URL of your Google Plus home page.
!!!{{{facebook}}}
Your Facebook ID, as entered on the settings page.
!!!{{{facebook_pageurl}}}
The URL of your Facebook home page.

!!{{{$subscriber}}}
The subscriber array contains a large number of elements allowing you enormous power in personalising your messages for individual recipients. It's all dependent on actually having that data, so make sure you populate as many of the fields we support as you need to produce the personalisation you need - for example we can't address someone by their first name if you didn't provide their first name when you uploaded your list. Most of the values in these fields must be provided by you - for example we don't split first and last names apart if you only provide a full name in one field; we don't extract initials from the middles of names etc.
!!!{{{email}}}
The most fundamental thing in email marketing; the subscriber's email address.
!!!{{{informal_greeting}}}
This is a dynamic name that should be used to address or greet a subscriber. It checks various name properties and assembles the best greeting it can from the available data. This is a better option than trying to use a simplistic {{{[[$subscriber.firstname]]}}} as it is more likely to produce a usable result. The 'informal' part refers to the fact that it will attempt to build a greeting using the subscriber's first name.
!!!{{{formal_greeting}}}
Works the same way as {{{informal_greeting}}}, but attempts to build a more formal address like 'Mr Smith'. Here's a table showing how these two elements can appear and interaction with various input field possibilities and the {{{default}}} modifier:
|!Template tag |!Dear|!Title|!Firstname|!Lastname|!Output|!Notes |
|{{{[[$subscriber.informal_greeting]]}}}||||||No data, no default, no output|
|{{{[[$subscriber.informal_greeting|default:"Subscriber"]]}}}|||||Subscriber|No data|
|{{{[[$subscriber.informal_greeting|default:"Subscriber"]]}}}||Mr|John|Smith|John|Basic firstname|
|{{{[[$subscriber.formal_greeting|default:"Subscriber"]]}}}||Mr|John|Smith|Mr Smith|Basic title + lastname|
|{{{[[$subscriber.formal_greeting|default:"Subscriber"]]}}}|||John|Smith|Subscriber|Missing title|
|{{{[[$subscriber.informal_greeting|default:"Subscriber"]]}}}||Mr||Smith|Subscriber|Missing firstname|
|{{{[[$subscriber.formal_greeting|default:"Subscriber"]]}}}||Mr|John||Subscriber|Missing lastname|
|{{{[[$subscriber.informal_greeting|default:"Subscriber"]]}}}|Johnny|Mr|John|Smith|Johnny|Dear overrides firstname|
|{{{[[$subscriber.formal_greeting|default:"Subscriber"]]}}}|Johnny|Mr|John|Smith|Johnny|Dear overrides formal address|
!!!{{{title}}}
A salutation title, such as "Mr", "Dr", "Señora" etc.
!!!{{{initials}}}
If someone presents initials in their name, you should put them in here, so for "Joan P. User" you would expect this to contain 'P', or perhaps "J. P." - it's up to you.
!!!{{{firstname}}}
The subscriber's first name. Before you think of using this, look at the {{{informal_greeting}}} element which provides a more reliable means of addressing your subscribers by name.
!!!{{{lastname}}}
The subscriber's last or family name. Before you think of using this, look at the {{{formal_greeting}}} element which provides a more reliable means of addressing your subscribers by name.
!!!{{{dear}}}
This is a very useful field for greeting your subscribers. It's not uncommon for people to be addressed by names that are not made up of their title, first and last names. For example they might prefer a nickname or political title that is not related to their real name, for example a certain Mr Barack Obama might have a {{{dear}}} element containing "Mr President" which can be used to address him without having to take the foolish measure of setting his last name to "President"! In [[signup forms|Subscribing]] you might want to simplify the layout and use the {{{dear}}} field to capture a full name instead of having separate first and last name fields. See the {{{informal_greeting}}} and {{{formal_greeting}}} elements which provide a more reliable means of addressing your subscribers by name.
!!!{{{jobtitle}}}
The subscriber's job title.
!!!{{{companyname}}}
The name of the company the subscriber works for.
!!!{{{address1}}}, {{{address2}}}, {{{address3}}}, {{{posttown}}}, {{{county}}}, {{{postcode}}}
Three free-form address fields and other individual address elements. See the {{{address}}} element for a more reliable way of displaying a subscriber's address.
!!!{{{country}}}
The subscriber's country name, generated using standard [[ISO3166|http://www.iso.org/iso/country_codes.htm]] names in English.
!!!{{{countrycode}}}
The subscriber's [[ISO3166-1 alpha-2|http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2]] country code, for example {{{GB}}} for the United Kingdom.
!!!{{{address}}}
A dynamically assembled concatenation of all the above address fields into a multi-line address, with blank lines left out. Use this with the {{{nl2br}}} and {{{stripbreaks}}} modifiers just as for {{{$account.address}}}.
!!!{{{phone}}}
The subscriber's phone number (land line).
!!!{{{mobile}}}
The subscriber's mobile number.
!!!{{{preferred_format}}}
It's possible for subscribers to set a choice of what format they want to receive messages in - either HTML or plain text. This will contain {{{html}}} or {{{plain}}} accordingly, defaulting to {{{html}}}. This isn't much use when you're already in an HTML or plain-text template context, but there's a remote possibility you might want this for something!
!!!{{{preferred_language}}}
An [[ISO639-1|http://en.wikipedia.org/wiki/ISO_639]] language name, in English that the subscriber has indicated they would like to receive messages in. This doesn't imply that messages are available in that language, but merely to indicate what the subscriber would prefer. Defaults to {{{en}}}.
!!!{{{preferred_languagecode}}}
A corresponding [[ISO-639-1 language code|http://en.wikipedia.org/wiki/ISO_639-1]], as above.
!!!{{{dob}}}
Date of birth in ~ISO8601 format.
!!!{{{sex}}}
{{{m}}}, {{{f}}} or {{{unknown}}}
!!!{{{url}}}
A subscriber's URL, for example to their personal blog or ~LinkedIn page.
!!!{{{landingpage_url}}}
This points to the same page as {{{$account.landingpage_url}}}, but contains an additional identifier to pre-populate the page with data and subscription info for this specific subscriber.
!!!{{{custom1}}}...{{{custom32}}}
These are custom fields for you to use as you like. They are limited to 255 characters, but beyond that you can put what you like in them.
!!!{{{id}}}
A unique identifying hash - this is a random-looking jumble of about 16 letters and numbers that can be used to uniquely identify this subscriber. It's useful for inserting into ad tracking ~URLs and 'cachebusters'. All messages sent to this subscriber will have the same id value. See {{{$message.id}}} for a finer-grained identifier.

!!{{{$campaign}}}
A campaign is a folder in which related mailshots can be grouped.
!!!{{{name}}}
Simply the name of the campaign folder.

!!{{{$mailinglist}}}
This represents the mailing list to which the mailshot is being sent.
!!!{{{id}}}
The ID of the list; a simple integer. This may be useful if you are feeding links into custom signup forms or our API dn need to identify this mailing list.
!!!{{{name}}}
The name of the mailing list. On a mailing list page you can set two names for a list, one internal, one public. This element contains the public one.
!!!{{{description}}}
A description of this mailing list.
!!!{{{subscribe_url}}}
The URL of our standard signup form for this list.
!!!{{{subscriber_count}}}
The number of subscribers on this list.

!!{{{$mailshot}}}
Info concerning this particular mailshot. Note the mailshot does not have a {{{subject}}} property. While the subject is set at the mailshot level, it is itself a template, and may not contain usable text until it is merged with data relating to a particular subscriber and message, meaning the subject may be different for every message. For this reason you will find it in the {{{$message.subject}}} variable.
!!!{{{id}}}
Much like {{{$mailinglist.id}}}, an integer identifier for this mailshot.
!!!{{{name}}}
The name of this mailshot.
!!!{{{date_sent}}}
The date and time this mailshot (as a whole) was sent, in [[ISO8601 format|http://en.wikipedia.org/wiki/ISO_8601]], {{{yyyy-mm-dd hh:mm:ss}}}.
!!!{{{timestamp}}}
The date and time this mailshot (as a whole) was sent, in unix timestamp format. Format this into a more friendly representation with the {{{date_format}}} modifier.
!!!{{{unsubscribe_url}}}
This is an important element and ''must be present in your template'' - if your template does not contain this element, ''it will be inserted automatically'' as part of an unsubscribe link just before the closing {{{</body>}}} tag. An unsubscribe link would normally look like this:
{{{
<a href="[[$mailshot.unsubscribe_url]]">Unsubscribe</a>
}}}
Unsubscribe ~URLs will not work in web versions since they are generic and do not relate to any particular subscriber, hence they can't sensibly provide an unsubscribe link.
!!!{{{webversion_url}}}
All mailshots get an automatic web version that recipients can use to view your message if the message they received is unreadable or corrupt in some way (e.g. due to mail filter actions); this URL points to it. This is a generic version of your mailing and does not contain personal information (it uses placeholder data instead), so it's safe to share on social networks, forward to friends etc. For a personalised version, see {{{$message.webversion_url}}}.
!!!{{{previewimage_url}}}
This contains a URL of an image of this mailing, as used on the send page, so your message can contain a picture of itself. The image is generated so that it will fit inside a 320x320 pixel square, though it may not fill the entire contents.
!!!{{{facebook_staticshareurl}}}
This is a URL used by Facebook to allow recipients to share the web version of this mailshot ith their friends. You should build this into a link, and it does not require javascript.
!!!{{{linkedin_staticshareurl}}}
Like {{{facebook_staticshareurl}}}, but for ~LinkedIn.
!!!{{{twitter_staticshareurl}}}
Like {{{facebook_staticshareurl}}}, but for Twitter.

!!{{{$message}}}
This holds properties of this individual message.
!!!{{{id}}}
A unique identifying hash - this is a random-looking jumble of about 16 letters and numbers that can be used to uniquely identify this individual message. It's useful for inserting into ad tracking ~URLs and 'cachebusters'. Every message will have a different value. See {{{$subscriber.id}}} for a coarser-grained identifier.
!!!{{{date_sent}}}
The date and time this message was sent, in [[ISO8601 format|http://en.wikipedia.org/wiki/ISO_8601]], {{{yyyy-mm-dd hh:mm:ss}}}.
!!!{{{timestamp}}}
The date and time this message was sent, in unix timestamp format. Format this into a more friendly representation with the {{{date_format}}} modifier.
!!!{{{tracking_url}}}
This a classic 'web bug' message opening tracker. It is a URL of a transparent 1-pixel GIF, and it allows us to be notified when a recipient opens a message. Your template must contain this in an image tag if it is to have message openings tracked. If you don't include this, a suitable image tag will be added automatically, but if you fnd that messes with your layout, you can add it yourself using this tag. A suitable tag would be:
{{{
<img src="[[$message.tracking_url]]" width="1" height="1" alt="*">
}}}
!!!{{{webversion_url}}}
This is just like {{{$mailshot.webversion_url}}}, but points to a fully-personalised web version of the message that was sent. Because this may contain personal information, we discourage the use of this URL as it is not suitable for public sharing and open access.
!!!{{{subject}}}
The subject of the message. Smartmessages subject lines are themselves complete templates - you can use nearly everything on this page in subject lines to generate them dynamically. Because of this, each message may have a different subject line, and this element will contain the one that this subscriber received. When this tag appears in a generic web version, it ends up blank, so we advise using a defaut modifier containing something that will work at the mailshot level or higher, for example:
{{{
<title>[[$message.subject|default:$account.name]]</title>
}}}
It's a good idea to set the title tag to the subject line like this as some spam filters mark you down if they do not match.

!!{{{$subscription}}}
!!!{{{verified}}}
The verification status of this subscriber's membership of this list. If they have successfully completed a double-opt-in verification this will contain {{{yes}}}. If they have requested a double opt-in verification but have not yet clicked the activation link, it will contain {{{no}}}. If they have joined this list by some other means (e.g. from a list upload or by copying from another list), this will contain {{{n/a}}}.
!!!{{{date_added}}}
The date this subscriber was added to this list in ~ISO8601 format.
!!!{{{ip}}}
The IP address that this subscriber used when they completed their double opt-in, useful for confirming to a recipient that they did in fact complete this process!

!Modifiers
As their name suggests, modifiers alter the output of tag in some useful way. There are [[many modifiers available|http://www.smarty.net/docs/en/language.modifiers.tpl]]. Modifiers are applied by using the pipe character ({{{|}}}) followed by the name of the modifier. Some modifiers can accept parameters which are added by appending a colon and the parameter values in single or double quotes (or a plain variable name).
If you use modifiers inside an HTML attribute, for example to apply URL escaping, it's important to use single quotes for the modifier parameters so that they do not get confused with the quotes used by the attribute itself, for example you'll have trouble with:
{{{
<a href="http://www.example.com/email=[[$subscriber.email|escape:"url"]]">
}}}
Do this instead:
{{{
<a href="http://www.example.com/email=[[$subscriber.email|escape:'url']]">
}}}
!!{{{default}}}
The default modifier substitues a default value in case the tag output is empty. For example if you address you subscriber like this:
{{{
Dear [[$subscriber.firstname]],
}}}
but your subscriber has no firstname stored, you'll end up with an uncomfortable-looking result:
{{{
Dear ,
}}}
The default modifier can solve this by substituting a default value, like this:
{{{
Dear [[$subscriber.firstname|default:"Subscriber"]],
}}}
which will render to this if the firstname field is empty:
{{{
Dear Subscriber,
}}}
You don't have to use literal text for the default value - you can use other variables too. For example something we often do is set the HTML title tag like this:
{{{
<title>[[$message.subject|default:$account.name]]</title>
}}}
The message subject doesn't exist for mailshot-level previews (because it can potentially be different for every recipient), so here we make it default to the account name instead.
!!{{{escape}}}
Escaping is treating certain characters specially so as to avoid breaking HTML or other markup scheme, and also as a security measure. Smartmessages automatically applies HTML escaping to all output fields, so generally you don't need to worry about this.
!!!Preventing escaping
There are situations where escaping is not wanted. For example, if you're including RSS content which contains pre-formatted HTML, you don't want it escaped or the HTML would not work. You can disable the automatic escaping using the {{{nofilter}}} keyword, like this:
{{{
[[$rssitem.contents nofilter]]
}}}
!!!Other escaping mechanisms
You might want to use a different escaping mechanism for other different contexts; the most common being to escape strings for safe inclusion in ~URLs, for example:
{{{
[["Is this a question?"|escape:"url"]]
}}}
would result in:
{{{
Is%20this%20a%20question%3F
}}}
which is safe to include in a URL.
Smarty provides [[other escaping mechanisms and additional parameters|http://www.smarty.net/docs/en/language.modifier.escape.tpl]].
!!{{{stripbreaks}}}
The stripbreaks modifier removes line breaks and replaces them with something else. This is especially useful for converting an unformatted multi-line full address into a formatted one. If your address is:
{{{
123 Station Road
Toytown
Toyland
TOY123
}}}
as plain HTML (using the {{{[[$account.address]]}}} template tag), that would appear like this, since HTML ignores line breaks:
{{{
123 Station Road Toytown Toyland TOY123
}}}
A better option would be to replace the breaks with commas and spaces:
{{{
[[$account.address|stripbreaks:", "]]
}}}
resulting in:
{{{
123 Station Road, Toytown, Toyland, TOY123
}}}
To keep the lines like the original text, you need to convert line breaks to HTML {{{<br>}}} tags, and there is a separate modifier for that, {{{nl2br}}} (short for "newline to br tag"). Since that modifier generates HTML, you also need to prevent escaping of the output:
{{{
[[$account.address|nl2br nofilter]]
}}}
!!{{{date_format}}}
This is a useful modifier for converting time formats, particularly from the unfriendly (but compact and efficient) unix timestamp format that we provide for message/mailshot send time. The parameter to this modifier describes various parts of the output time format and uses them for reformat the output as requested: for example {{{%Y}}} represents a 4-digit year, {{{%B}}} is the full month name etc. These special characters are defined in the [[PHP strftime function|http://www.php.net/strftime]]. Example:
{{{
This message was sent on [[$message.timestamp|date_format:"%A ,%e %B, %Y"]].
}}}
Will render to:
{{{
This message was sent on Monday, 26 May, 2014.
}}}

!Function tags
Function tags provide dynamic output that cannot easily be provided by static field values. In addition to the [[standard functions that Smarty includes|http://www.smarty.net/docs/en/language.custom.functions.tpl]], Smartmessages provides some additional functions. Function tags accept parameters as space-separated pairs. Any modifiers you want to apply to the tag should appear immediately after the tag name, before the parameters.
!!{{{button}}}
It is messy and complicated to generate buttons in HTML that will work in email, so Smartmessages provides a tag that can generate them for you based on two popular online button generators. There are lots of options:
* {{{url}}}: a URL to link to; defaults to {{{$account.url}}}
* {{{text}}}: The button label; ''must be provided''
* {{{bg}}}: a background colour (CSS hex); defaults to {{{#333}}} (dark grey)
* {{{fg}}}: a foreground colour (CSS hex); defaults to {{{#fff}}} (white)
* {{{width}}}: width in pixels; defaults to 140.
* {{{height}}}: height in pixels; defaults to 40.
* {{{textsize}}}: font size in pixels; defaults to 13.
* {{{border}}}: border colour (CSS); defaults to no border.
* {{{radius}}}: Border radius in pixels; defaults to 0 (square corners)
* {{{fonts}}}: A CSS font stack to use for the button label; defaults to "'Helvetica Neue', Helvetica, Arial, sans-serif"
* {{{type}}}: 1 for [[buttons.cm|http://buttons.cm]] rendering, 2 for [[industrydive|http://www.industrydive.com/blog/how-to-make-html-email-buttons-that-rock/]]; defaults to 1
Example:
{{{
[[button text="Visit our home page" bg="#e00" fg="#eee"]]
}}}
!!{{{facebook_like}}}
Facebook "Like" buttons are slightly complicated since they need to be different for web and email versions. This tag generates markup that works correctly in both contexts. It takes a single optional {{{url}}} parameter that defaults to the URL of the web version of the mailshot, but you can specify it manually so recipients can 'like' any URL you want.
!!{{{googleplus_share}}}
Much like a Facebook "Like" button, the google "+1" button the needs to be different in email and web versions, and again takes only a single optional {{{url}}} parameter that defaults to the URL of the web version of the mailshot. Unfortunately Google does not provide a supported static version of the "+1" button function so it can't work in email, and this tag produces no output.
!!{{{linkedin_share}}}
~LinkedIn's equivalent of a Facebook "Like" button, and again takes only a single optional {{{url}}} parameter that defaults to the URL of the web version of the mailshot.
!!{{{twitter_share}}}
Again like a Facebook "Like" button, and again takes only a single optional {{{url}}} parameter that defaults to the URL of the web version of the mailshot.
!!{{{twitter_follow}}}
Adds a Twitter "Follow" button using the Twitter ID set in your settings page.
!!{{{gravatar}}}
Gravatars are automatic icons generated from email addresses, [[documented here|Gravatars]]
!!{{{rss}}}
The RSS tag is documented [[here|RSS]].
!!{{{toc}}}
This is the "table of contents" tag. This processes the finished output of your template and generates a list of intra-page links (that is, links that point at different areas of the same page, not other pages) in the form of nested HTML {{{<ul>}}} tags. Because it processes the finished template, it can include items generated from RSS feeds. It scans your template for HTML heading tags ({{{h1}}} to {{{h6}}}) and accepts several parameters to control the output:
* {{{start}}}: The heading level the TOC should start at; defaults to 1 for {{{h1}}}.
* {{{levels}}}: How many levels deep to go from the start level; defaults to 3 ({{{h1}}} to {{{h3}}}).
* {{{flat}}}: a boolean option - whether to generate a nested table of contents or a single-level flat structure; defaults to false.
* {{{container}}}: an HTML element id to restrict the scan to. If this is specified, the TOC will only be generated from heading tags occuring inside this element; defaults to empty (no restriction).
Example: this would generate a 2-level TOC pointing at h2 and h3 tags in the entire document:
{{{
[[toc start=2 levels=2]]
}}}
This would generate a TOC of all h1 items found inside an element with an id of 'rss':
{{{
[[toc container="rss" start=1 levels=1]]
}}}
It only includes heading tags that have an HTML {{{id}}} attribute set - otherwise they cannot be targeted, but if you have a heading tag that has an ID but you don't want it in the TOC, add the {{{notoc}}} class to its element, for example {{{<h3 id="someheading" class="notoc">Heading</h3>}}}, and it will be skipped.
!!{{{share}}}
In addition to service-specific tags for link sharing (such as {{{facebook_like}}}), Smartmessages includes a more general sharing tag that's not quite so clever, but supports many more services. It's called {{{share}}} and it takes the following parameters:
* {{{url}}} By default the URL of the mailshot's web version will be shared, but you can override it by providing a URL parameter.
* {{{on}}} A quoted, comma-delimited list of services to generate sharing links for. If you don't specify any, or specify 'top', only the top 5 sharing sites will be used. You can spceify 'all' to show links to all the all the links will be included (though that's quite a lot!). Dots, spaces, dashes and capitalisation are ignored, so for example 'delicious' and 'del.icio.us' are considered equivalent, and duplicates will be ignored. We have lots to choose from:
** Blinklist
** Del.icio.us
** Designfloat
** Digg
** Diigo
** ~DZone
** Facebook
** Folkd
** Google
** ~LinkedIn
** ~MisterWong
** ~MySpace
** Netvouz
** Newsvine
** Pinterest
** Reddit
** Slashdot
** ~StumbleUpon
** Technorati
** Twitter
** Webnews
** Yahoo
* {{{using}}} How the generated links should be displayed. The default is 'icons16x2', and the options are:
** {{{icons16}}} Display small 16-pixel icons.
** {{{icons16x2}}} Display small retina-ready 16-pixel icons.
** {{{icons32}}} Display larger 32-pixel icons.
** {{{icons32x2}}} Display larger retina-ready 32-pixel icons.
** {{{icons64}}} Display big 64-pixel icons.
** {{{links}}} Display text links.
* {{{title}}} The title to use when sharing, defaults to the message's subject line.
For example, this would share the mailshot's URL and subject line to Facebook, Digg and Pinterest:
{{{
[[share on="facebook,digg,pinterest"]]
}}}
and this would share your home page URL to all services using links:
{{{
[[share url=$account.url on="all" using="links" title=$account.name]]
}}}
Smartmessages has an enormously powerful templating system based on the popular [[Smarty templating framework|http://www.smarty.net]], and much of [[the documentation|http://www.smarty.net/docs/en/]] on that will apply to Smartmessages templates too.
!Literal text
The majority of a template is made up of straight HTML or plain text that is left untouched. Variables, modifiers and other tags may be used to add, substitute or alter parts of the template in order to render it into a complete message destined for an individual recipient.
!Tags
A tag is a dynamic element that will render to some useful value, such as the recipient's name, when each message is generated. Tags are delimited by {{{[[}}} and {{{]]}}} to distinguish them from literal text, so a typical tag will look something like {{{[[thisisatag]]}}}. You can only use tags that are defined - using non-existent tags will result in an error and you won't be able to send your mailshot; all the tags we support are listed in the template guide.
!Variables
Smartmessages provides several useful variables that are actually arrays of multiple items. These allow us to group related items together in a consistent way. Variables are denoted by a {{{$}}} sign at the start of their name, for example {{{$account}}} is the variable which contains items relating to your Smartmessages account. Individual elements within an array variable are accessed by name, so the name of your account can be found in {{{$account.name}}}, and a complete tag that will render to your account name would be {{{[[$account.name]]}}}. Variables are most often used in plain tags when you just want to display their value, but you can also use them in other places, such as in conditions (see below). You'll find a complete listing of variables and the elements they contain in the template guide.
!Modifiers
As their name suggests, modifiers are applied to variable values in order to change them in same way as the template is rendered. A typical modifier might be {{{upper}}}, which converts the value to upper case letters. Modifiers are applied using the pipe character: {{{|}}} followed by the modifier name and are placed after whatever they are modifying, so to capitalise your account name you could say: {{{[[$account.name|upper]]}}}. Some modifiers can be given parameters to change their behaviour; these are provided by following the modifier name with a colon, then the parameters in double quotes. For example the {{{escape}}} modifier applies HTML escaping by default, but it can be switched to other forms of escaping via a parameter; {{{[[$account.name|escape:"url"]]}}} will apply URL escaping, useful for inserting un-encoded values into ~URLs safely. Multiple modifiers can be chained one after the other, so to capitalise  AND apply URL escaping to your account name you would say {{{[[$account.name|upper|escape:"url"]]}}}. You can apply modifiers to literal text by putting the text in a tag like this: {{{[["this is some text"|upper]]}}} and it will process the literal text just like it does for variables. Some native PHP functions can also be used as modifiers, for example 
There's a full list of available modifiers in the template guide.
!Function tags
Smartmessages provides some special function tags to produce dynamic content in a much more compact form than is possible with simple strings. For example, you can create a fully operational Facebook 'Like' button simply by saying {{{[[facebook_like]]}}}. Much like modifiers, function tags can often accept parameters, for example the {{{[[gravatar]]}}} function tag accepts optional {{{size}}}, {{{email}}}, {{{rating}}} and {{{default}}} parameters to change its output. The full list of these special tags is in the template guide.
!Complex tags
There are some tags that have higher levels of complexity. The most useful of these is the RSS tag, which allows you to render RSS feeds dynamically in your templates. This can save an awful lot of cutting and pasting if you have, for example, a company blog that you want to republish stories from in your mailshots. There is [[complete documentation on the RSS tag|RSS]].
!Conditions
Conditions are a kind of control structure that allows you to show or not show sections of your template to your recipients based on any value you like, and they are an important feature for personalisation. Conditions are built with the {{{if}}} tag. They are a bit more complex than simple tags because they have a opening and a closing tag - every opening {{{[[if]]}}} tag must have a matching closing {{{[[/if]]}}} tag. Here's a simple example:
{{{
[[if $subscriber.firstname == 'Bob']]
Wow! You're called Bob! That means you get to see this secret paragraph!
[[/if]]
}}}
You can see the {{{[[if]]}}} tag is inspecting the value of {{{$subscriber.firstname}}} to see if it is equal to 'Bob' (notice that it uses //two equals signs//, not just one), and if it is, it will show the content that occurs between the opening and closing tags, and people who are not called Bob will not see it. You can inspect any value provided in the variables, and use many operators and functions on the compared values. {{{[[if]]}}} has some further tricks up its sleeve in the form of {{{else}}} and {{{elseif}}} tags. The else tag allows you to say what to do if something does //not// match what the {{{if}}} tag checks for, for example:
{{{
[[if $subscriber.firstname == 'Bob']]
Wow! You're called Bob! That means you get to see this secret paragraph!
[[else]]
Have you considered changing your name to Bob?
[[/if]]
}}}
{{{elseif}}} allows you to check for further conditions before giving up:
{{{
[[if $subscriber.firstname == 'Bob']]
Wow! You're called Bob! That means you get to see this secret paragraph!
[[elseif $subscriber.firstname == 'Carol']]
Yay, you're in the Carol club!
[[else]]
Have you considered changing your name to Bob or Carol?
[[/if]]
}}}
Here people called Bob will see the first paragraph, people called Carol will see the second, and everyone else will see the third (but Bob and Carol will not see the third).
!!Nesting
You can also nest these structures one inside the other, but make sure you close  tags in the right order or you'll get a template error:
{{{
[[if $subscriber.firstname == 'Bob']]
Wow! You're called Bob! That means you get to see this secret paragraph!
[[if $subscriber.lastname == "Smith"]]
Surely not <em>the</em> Bob Smith??
[[/if]]
[[/if]]
}}}
!!Operators
When doing your comparisons there are several useful operators to choose from:
|!Operator|!Meaning|
| {{{==}}} |Equal to|
| {{{!=}}} |Not equal to|
| {{{>}}} |Greater than|
| {{{<}}} |Less than|
| {{{>=}}} |Greater than or equal to|
| {{{<=}}} |Less than or equal to|

You can also use logical operators to compare multiple values at once, for example:
{{{
[[if $subscriber.firstname == 'Bob' and $subscriber.lastname == "Smith"]]
Wow! You're called Bob Smith!
[[/if]]
}}}

Available logical operators include {{{and}}}, {{{or}}}, {{{not}}} and others, and they can be grouped and isolated using parentheses (round brackets), for example:

{{{
[[if ($subscriber.firstname == 'Bob' or $subscriber.firstname == 'Carol') and $subscriber.lastname == "Smith"]]
Wow! You're from the Smith family!
[[/if]]
}}}

!Mathematicical operations
You're not limited to text processing - you can do mathematical operations too, for example if you have put a product price in the {{{$subscriber.custom1}}} property you could do this:

{{{
Regular price £[[$subscriber.custom1]], your special discount price only £[[$subscriber.custom1*0.8]]!!!
}}}

You can use or apply mathematical operations pretty much anywhere it's appropriate, including by themselves, e.g. {{{[[2+2]]}}}

!PHP Functions
Many native PHP functions can also be used inside tags either in calculating conditions or mathematical operations, or as modifers, for example {{{[["Hello"|strtolower]]}}} will result in "hello".
We offer complete support for user-uploadable templates, so you can paste in your HTML, or grab it from a web page, o insert it via our API. Templates can make use of all of our dynamic features.

//You don't necessarily need to create your own templates. Our standard templates provide straightforward WYSIWGYG editing without any HTML authoring required. You only need to use this feature if you're familiar with HTML email and want to code your own.//

The templates tab allows you to add a new template, then add your contents. You can provide both HTML and plain-text versions, which you can grab from ~URLs if you happen to have posted your templates on a web server somewhere, though be aware that the web and email are really quite different, and most web pages will not work well as emails.
!!On the templates page...
You'll see a list of your templates (which will initially be empty) on the left hand side - click the 'Add' button below the list to create a new template.

Each template can have a title (which will appear in menus and informational screens, such as the mailings tab). The description is used in the template gallery to provide a longer description of that particular template's features or purpose.

The subject line is a default and may be overridden on the send page (and you can use the same syntax for field insertion in the subject as in the HTML and plain text bodies, though bear in mind that HTML tags won't work.
A template provides the overall look and layout of the content of the messages you send through Smartmessages.
Out of the box, we provide some standard basic templates for a blank message (you provide all content), a simple note, an invitation to an event, and a newsletter. These are ready to roll, and if you have paid for our account setup service, you'll find these automatically make use of your custom headers and footers. Each template can contain multiple editable elements - a simple note has a single content area, but a newsletter has four, and the send page will automatically show the options to match the template.
These templates also include a plain text version, an automatic link to a web version (for those who can't or have trouble receiving HTML email), a link to subscription options (unsubscribe etc), and an opening tracking image.
!!What's special about Smartmessages templates?
The big difference between our templates and those of most other providers is that they can be reused. Instead of having a designer create a new template for you every month, you can get them to do it just once, and then you can just change the content every month without having to go back to your designer every time. We feel that anything less doesn't really justify the name 'template' - why go to all that effort and testing for a single mailing?
We can build custom templates for you from scratch, or you can provide us with your version and we can adapt it for you, or you can do it all yourself through our [[templates|TemplateUploads]] page.
!!Creating your own templates
If you're creating your own templates using our templates page, there are some things you need to be aware of when designing HTML for email.
!!!Email is not the web
Many people forget this and expect HTML email to act just like a web page. It won't. Here are some things you need to look out for:
* ''Old-school HTML 4 rules''. With the recent ravages of MS Outlook 2007, CSS in email is now mainly a recipe for disaster. While there are still ways you can use it, it's so convoluted to make it work reliably that it's not really worth the hassle. This means that you should dust off those old articles about sliced images, tables for layout and font tags - they are the only things that work reliably and easily. Email now is like the web was in 1997.
* ''All ~URLs must be absolute'' - if your links or images don't start with 'http', they are probably wrong. On a web page these ~URLs are considered relative to the current page, but when you're looking at an email, the message doesn't have a URL, so those ~URLs will not work.
* ''No external resources'' - that means no external scripts or style sheets.
* ''Don't use the <base> tag'' - it can work, but will also break in some clients (hotmail), and will prevent you from using intra-page links (href="#...").
* ''No text over images''. One of Outlook 2007's most heinous acts is to make it impossible to place text on top of images because table cell backgrounds don't work in it.
* ''Web pages are not email messages''. The majority of web pages presented directly as emails simply won't work, so don't expect them to.
* ''Imported templates are filtered'' before use. If we didn't filter javascript and various other aspects, we could become a vector for [[XSS attacks|http://en.wikipedia.org/wiki/Cross-site_scripting]].
In order to work within these restrictions, we have some other suggestions which are mainly just good practice for web, email and informational design:
* ''Use our checkers'' - On the templates page you'll find a whole array of tools to check your links, images markup and spam rating - ''Use them!''
* ''Validate your HTML'': Invalid HTML is just wrong, no exceptions - if you have cross-client layout problems, check the HTML is valid first - Use the validation link in our template editor, or alternatively the [[Total Validator|https://addons.mozilla.org/firefox/addon/2318]] plugin for Firefox is a good tool for this, or paste a rendered message (our web preview is a good source for this) into the [[W3C validator|http://validator.w3.org/]].
* Make sure your message remains meaningful and legible with images disabled since that's the default in the majority of email clients. //Always// use alt attributes on your images, and make them meaningful as they often displayed when images are off. Our template editor has a 'preview without images' link for testing how this looks.
* ''Check your content'': We provide link and image testing options in the template editor, which will tell you about broken links, missing, misformatted or mis-sized images. We also provide a ~SpamAssassin-based spam checker and some simple tests against our own set of useful rules.
* ''Keep it simple'' - email clients are not modern browsers; keep your stories short and messages clear, link to more complex content (clicks are your marketing intelligence goldmine). Smaller, simpler messages send faster, bounce less, open faster, dilute recipients precious attention less.
* Provide a plain text version of your template - some users //like// living in the last century...
* Use background colours to add colour to your messages when there are no images.
* ''TEST, TEST, TEST'' in all email clients you expect your message to be read in - you'll probably need yahoo, hotmail, gmail accounts, and Outlook 2003, Outlook 2007, Apple Mail, Eudora, Evolution and Thunderbird desktop clients. Alternatively, use a service like [[Litmus|http://www.litmusapp.com]] for cross-client testing.
* You don't have to do anything special to get your links tracked - just leave them as they are. You can also [[suppress link tracking|Link Tracking]] on a per-link basis.
* To insert dynamic data within your templates, use the macros available in the template editor pop-up.
* If you stick to straightforward HTML with no javascript and no external references (other than images), you shouldn't run into any issues with filtering.
!![[The Daily Telegraph|http://www.telegraph.co.uk/]]
We recently undertook a data protection and list cleaning exercise for the Daily Telegraph for their list of two million readers.
!!Daily Telegraph Fantasy Football
We ran last season's fantasy football mailings to their list of 200,000.
!![[Guitar Amp & Keyboard|http://www.guitarampkeyboard.com/]]
One of Europe's biggest music retailers, we handle their list of 140,000
!![[Woolovers|http://www.woolovers.com/]]
A rapidly-growing business that's getting great results:
//"The broadcast of the email started at just after 9pm last night finishing around 10.45pm.
Number of sales for both today and yesterday 142 & 102 just for today. If you look at the same time period for last week we only had 31 orders for the two days. A big success so far.  Let's see how many more orders flow through over the weekend. A big thank you to all of you for the help you gave me in setting up this fantastic marketing tool."//
Unsubscribing is the act of removing a subscriber from a mailing list. This could be accomplished by clicking a link, submitting a web form, sending an email to a special address, calling an [[API]] or some combination of those actions. By default, Smartmessages provides a simple unsubscribe form that's linked from every message we send, so you don't need to do anything in order to have full unsubscribe handling.
!!Why support unsubscribes?
You might think that the last thing you want is to make it easy for someone to get off your list, but there are major advantages (quite apart from your legal obligations) to doing so. Put it like this - every time someone unsubscribes, the quality of your list goes up; it contains proportionally less people that don't want to hear from you and more that do. If you persist in sending messages to someone that doesn't want them, they will most probably [[report your message as spam|SpamReports]], which can reduce the deliverability of ALL your other messages, or even get you blacklisted/blocked in the worst case. That's not good. As well as being illegal in many countries, it's also not polite, so do what your subscribers ask.

To comply with data protection laws, including the US ~CAN-SPAM act, It's vital that every message sent to a list includes an unsubscribe link. We always process unsubscribes immediately - if you have a mailshot scheduled to send and someone unsubscribes 1 second before it is sent, they will not be mailed, which also exceeds the requirements of ~CAN-SPAM.

All our standard templates include an unsubscribe link, and you can add one to your own templates in our [[template editor|TemplateUploads]] - if you don't add one, we'll put one in automatically since it's a legal requirement.
!!Standards support
Because links can sometimes go astray or become corrupted within the body of a message by mail/spam filters, we include something called a "~List-Unsubscribe" header in every message, as described [[here|http://www.list-unsubscribe.com/]] and in the [[RFC 2369|http://www.faqs.org/rfcs/rfc2369.html]] standard. These provide a simple and consistent way for subscribers to remove themselves from your lists, are much more likely to make it through filters intact, and are supported by an increasing number of the major email services, most notably Windows Live Mail, Gmail and Yahoo!
!!Who has unsubscribed?
You can get a list of users that have unsubscribed from each of your lists on our contacts page - just click the 'Download Unsubscribes' button to get a simple CSV format list. If you maintain a list of subscribers outside of Smartmessages, you can use that list to remove those that have unsubscribed.

When someone unsubscribes from a list, we automatically [[suppress|SuppressionLists]] them from uploads to that list so that they do not get inadvertently re-subscribed against their wishes.
!!Unsubscribe processing options
There are several options available for handling unsubscribes in Smartmessages:
*Use our standard unsubscribe links and forms
*Use our standard link and forms customised with with your own [[branding|Branding]]
*Build your own unsubscribe form (see below for details) and let us process it
*Build your own form, process it youself (for example so you can add an unsubscribe survey), and notify us of the unsubscribe via our form processor or an API call.
!!Custom unsubscribe forms
In a similar way to [[automating subscribes|Subscribing]], you can automate our unsubscribe forms too. Many of our customers want to use their own web forms to submit information to our unsubscribe processor, and that's also easy to do - just point your form's action at https&#58;&#47;/www.smartmessages.net/unsubscribe.php. This approach is much simpler than using our API.
The form processor can make use of the following parameters (bold fields are required):
*''//command//'' - the value 'unsubscribe'
*''//emailaddress//'' - the email address of the new subscriber
*''//mlid//'' - the ID of the mailing list they are unsubscribing from (this is the value that's visible in the "link" links on the contacts page)
*//redirect// - a URL to go to after processing the subscription request instead of displaying our own default page (don't forget to ~URL-encode it)
*//json// - any value - its presence will indicate that you want a response in a JSON format (e.g. you're unsubscribing from ~JavaScript)
By building your own form and providing a redirect URL, you can use our unsubscribe processor completely transparently and invisibly to your users. If you specify a redirect URL, you will receive an HTTP GET request to that URL containing your submitted parameters (so you can process it further - for example remove it from your own contact database) along with additional information about the success (or not) of the subscription request in a 'statuscode' parameter. This has the following meanings:
*1 - Success
*3 - Mailing list not found
*5 - Missing required parameters
*6 - Already unsubscribed
*7 - Already globally unsubscribed
*8 - Placeholder link followed
*9 - Invalid email address
The JSON output also includes a 'success' value that is true for codes 1, 6 and 7, false for others, along with a 'message' value containing a plain-text explanation of the response.
Here's a minimal example form that you can adapt:
{{{
<form action="https://www.smartmessages.net/unsubscribe.php" method="post">
  <p>
    <input type="hidden" name="command" value="unsubscribe" />
    <input type="hidden" name="mlid" value="<insert your mailing list ID here>" />
    Email address: <input type="text" name="emailaddress" />
    <input type="submit" name="Unsubscribe" />
  </p>
</form>
}}}
Note that if your account has global unsubscribes turned on, it doesn't matter which mailing list ID you use, so long as it's one of yours.
!!Further automation
It's possible to change the ordering of this process. If you handle the request yourself, you can then make a behind-the-scenes request to our processor to tell us about it. We recommend that you make use of the json parameter in that case as it will provide the quickest and simplest response.
To upload a list click on the ''contacts'' tab.  [IMG[images/contacts.gif]]

Choose the ''Manage'' button next to the list you want to upload into:  [IMG[images/manage.gif]]

Click the ''Actions'' tab:

[IMG[images/upload.gif]]

Choose the ''Upload a list'' tab

[IMG[images/upload2.gif]]

When uploading a list there are some options.
!!Source
It's important to be able to keep track of where the email addresses in your account came from, so when you fill this field in, anyone who is added to a list will have this note attached to their history.
!!Overwrite existing data
When you add someone to a list, there are three scenarios:
* They are already on this list
* They are not on this list, but they are on other lists of yours, or have been in the past
* They are new, and you've never seen them before
The first case is simple - we do nothing, leaving their data untouched. In the second case we add them to this list, but don't touch any of their other data. In the third case, we simply add all the data you upload attached to the new address.
Now imagine that the data that is already in the system is wrong, or outdated and you want to replace what's there. For example if you have live data in custom fields that is used every week, you would want to check this box, similarly if you uploaded data that was incorrect (e.g. you got firstname and lastname the wrong way around).
!!Replace existing list
Normally a list upload only adds to a list. If you check this box, anyone not included in the list you upload will be removed. So if there are 10,000 subscribers on this list and you upload a list of 10 new ones and check this box, you will end up with a list with only 10 people on it - so tread carefully!
!!First line contains field names
Checking this box means that the import expects the first line of the CSV file to be a list of the column names, such as first name, email address, etc

!!Upload history
Lists are uploaded from a queue, so the upload history gives feedback of the status and once uploaded, information on the list.

[IMG[images/uphistory.gif]]

Roll over the column names on the live system to have an explanation of the finer detail.
<!--{{{-->
<div class='toolbar' role='navigation' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
When you send a mailshot, we include a link to a web version of it (using the {{{[[$mailshot.webversion_url]]}}} [[template tags|TemplateGuide]]), typically placed at the very top of your message as a simple text link, that will open in a recipient's web browser instead of their email program.

!!Why provide a web version link?
Web version links are commonly used by recipients for these main reasons:
#The message they received has been corrupted/rewritten/filtered in some way, rendering it unreadable or degraded in some way.
#They have images turned off and don't want to turn them on to view images (usually for security or privacy reasons)
#Their email program doesn't support HTML, e.g. it's on an older mobile phone, or a text-only email program such as Eudora or Mutt.
All of these have one thing in common - when the recipient looks at your message, they are already seeing a degraded version in one way or another; the web version link is thus provided as a last-ditch attempt to help those recipients to see what you intended.

The first of these is common if you're sending to business customers with outsourced email systems and web-mail clients such as gmail, both of which are often heavy-handed with their mail filtering, sometimes to the point of completely destroying messages. Ideally messages would not get corrupted, but they do (in ways we have no control over), so we need to be defensive and put additional effort into making sure that these links work when others might not. The biggest factor in URL breakage is length. Many breakages are down to ~URLs being too long - and in a environment of multiply-nested ad tracker links with large, messy parameter lists (I'm looking at you, tradedoubler!), these can often get to several hundred characters long. So we keep web version ~URLs as short as possible. The biggest thing we typically add to a URL is a user identifier, and by removing that we save up to 40 characters - and when a length limit may be less than 72 characters, that's enough to make the difference.

Many users fall into the second group because that's the default behaviour of most email clients, whether desktop, web or mobile. Users that are already sufficiently paranoid about security and privacy will also be fully aware of identifiers in ~URLs, so will not click a link with obvious embedded data in anyway, even if it would provide a better looking result.

The third reason probably has a lot of overlap with the second - many privacy advocates are also keen users of text-only mail programs - but many such users actively prefer text-only programs as they are significantly smaller and faster than the likes of Outlook. It's also another reason to make sure you produce a properly formatted plain-text version of your template.

There are other factors too: by serving a static, generic web version of your mailshot we can dramatically improve performance, probably by a factor of over 100. Our re-usable templates are a big feature that's unique to us - but what happens if you change your template after you've sent it (perhaps when preparing for your next send)? By keeping a 'frozen' copy of the mailshot, we avoid that problem, and it also acts as a kind of audit on exactly what was sent - if someone makes a mistake there's no covering it up!

We'd estimate that typically less than 1% of recipients are affected by these factors, so we're already talking about a very small proportion - a recent analysis showed around 1 in 500 users who opened the messages used the web version, which only accounted for about 1 in 20000 of the overall mailing. Of course you want to catch them all, but it's important to keep some perspective - do you consider it more important that someone sees your message, or that you know that they have done so, when trying to keep that connection may mean they don't see it at all? The time and effort required to chase down these last few people is much better spent on the probably >75% that don't log any activity at all!

For all of these reasons, in common with most other email marketing services, we don't do personalisation of web versions by default in an effort to keep the links working, avoid exposing personal data, and avoid turning away otherwise perfectly good customers.
!!Personalised web versions
Despite the above, we do provide the ability to override this behaviour in your own templates. Normally we use the equivalent of the generic {{{[[$mailshot.webversion_link]]}}} template tag; there is a personalised alternative in {{{[[$message.webversion_url]]}}} which will provide a link or ~URL to a fully personalised web version, however you should only use this with the full understanding that:
* You may potentially expose personal data, at your own risk
* It's much less likely to work
* Performance will be reduced
* Privacy-aware recipients are less likely to use it
* You can be defeating the entire point of having a web version at all
In short ''we strongly recommend you do not do this''.
<<tiddler SiteTitle>> - <<tiddler SiteSubtitle>>