mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 03:26:04 +01:00
Notification delivery (#6072)
* ability for users to choose how they receive notifications add type field to more notifications, the type field is used to determine what to do based on user setting(none,notification,email,notificationemail) * change var name to types * cleanup * add event types for privileged users * remove unused language keys * fix uids check * changed if statements * upgrade script to preserver old settings
This commit is contained in:
committed by
GitHub
parent
e68e5122e2
commit
dd176dd5f2
@@ -10,6 +10,7 @@
|
||||
"continue_to": "Continue to %1",
|
||||
"return_to": "Return to %1",
|
||||
"new_notification": "New Notification",
|
||||
"new_notification_from": "You have a new Notification from %1",
|
||||
"you_have_unread_notifications": "You have unread notifications.",
|
||||
|
||||
"all": "All",
|
||||
@@ -50,5 +51,20 @@
|
||||
"email-confirmed": "Email Confirmed",
|
||||
"email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
|
||||
"email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
|
||||
"email-confirm-sent": "Confirmation email sent."
|
||||
"email-confirm-sent": "Confirmation email sent.",
|
||||
|
||||
"none": "None",
|
||||
"notification_only": "Notification Only",
|
||||
"email_only": "Email Only",
|
||||
"notification_and_email": "Notification & Email",
|
||||
"notificationType_upvote": "When someone upvotes your post",
|
||||
"notificationType_new-topic": "When someone you follow posts a topic",
|
||||
"notificationType_new-reply": "When a new reply is posted in a topic you are watching",
|
||||
"notificationType_follow": "When someone starts following you",
|
||||
"notificationType_new-chat": "When you receive a chat message",
|
||||
"notificationType_group-invite": "When you receive a group invite",
|
||||
"notificationType_new-register": "When someone gets added to registration queue",
|
||||
"notificationType_post-queue": "When a new post is queued",
|
||||
"notificationType_new-post-flag": "When a post is flagged",
|
||||
"notificationType_new-user-flag": "When a user is flagged"
|
||||
}
|
||||
|
||||
@@ -84,8 +84,6 @@
|
||||
"digest_daily": "Daily",
|
||||
"digest_weekly": "Weekly",
|
||||
"digest_monthly": "Monthly",
|
||||
"send_chat_notifications": "Send an email if a new chat message arrives and I am not online",
|
||||
"send_post_notifications": "Send an email when replies are made to topics I am subscribed to",
|
||||
"settings-require-reload": "Some setting changes require a reload. Click here to reload the page.",
|
||||
|
||||
"has_no_follower": "This user doesn't have any followers :(",
|
||||
|
||||
@@ -84,13 +84,19 @@ settingsController.get = function (req, res, callback) {
|
||||
plugins.fireHook('filter:user.customSettings', { settings: results.settings, customSettings: [], uid: req.uid }, next);
|
||||
},
|
||||
function (data, next) {
|
||||
getHomePageRoutes(userData, function (err, routes) {
|
||||
userData.homePageRoutes = routes;
|
||||
next(err, data);
|
||||
});
|
||||
},
|
||||
function (data) {
|
||||
userData.customSettings = data.customSettings;
|
||||
async.parallel({
|
||||
notificationSettings: function (next) {
|
||||
getNotificationSettings(userData, next);
|
||||
},
|
||||
routes: function (next) {
|
||||
getHomePageRoutes(userData, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results) {
|
||||
userData.homePageRoutes = results.routes;
|
||||
userData.notificationSettings = results.notificationSettings;
|
||||
userData.disableEmailSubscriptions = parseInt(meta.config.disableEmailSubscriptions, 10) === 1;
|
||||
|
||||
userData.dailyDigestFreqOptions = [
|
||||
@@ -149,6 +155,56 @@ settingsController.get = function (req, res, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function getNotificationSettings(userData, callback) {
|
||||
var types = [
|
||||
'notificationType_upvote',
|
||||
'notificationType_new-topic',
|
||||
'notificationType_new-reply',
|
||||
'notificationType_follow',
|
||||
'notificationType_new-chat',
|
||||
'notificationType_group-invite',
|
||||
];
|
||||
|
||||
var privilegedTypes = [];
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.getPrivileges(userData.uid, next);
|
||||
},
|
||||
function (privileges, next) {
|
||||
if (privileges.isAdmin) {
|
||||
privilegedTypes.push('notificationType_new-register');
|
||||
}
|
||||
if (privileges.isAdmin || privileges.isGlobalMod || privileges.isModeratorOfAnyCategory) {
|
||||
privilegedTypes.push('notificationType_post-queue', 'notificationType_new-post-flag');
|
||||
}
|
||||
if (privileges.isAdmin || privileges.isGlobalMod) {
|
||||
privilegedTypes.push('notificationType_new-user-flag');
|
||||
}
|
||||
plugins.fireHook('filter:user.notificationTypes', {
|
||||
userData: userData,
|
||||
types: types,
|
||||
privilegedTypes: privilegedTypes,
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
function modifyType(type) {
|
||||
var setting = userData.settings[type] || 'notification';
|
||||
|
||||
return {
|
||||
name: type,
|
||||
label: '[[notifications:' + type + ']]',
|
||||
none: setting === 'none',
|
||||
notification: setting === 'notification',
|
||||
email: setting === 'email',
|
||||
notificationemail: setting === 'notificationemail',
|
||||
};
|
||||
}
|
||||
var notificationSettings = results.types.map(modifyType).concat(results.privilegedTypes.map(modifyType));
|
||||
next(null, notificationSettings);
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function getHomePageRoutes(userData, callback) {
|
||||
async.waterfall([
|
||||
|
||||
@@ -696,6 +696,7 @@ Flags.notify = function (flagObj, uid, callback) {
|
||||
}
|
||||
|
||||
notifications.create({
|
||||
type: 'new-user-flag',
|
||||
bodyShort: '[[notifications:user_flagged_user, ' + flagObj.reporter.username + ', ' + flagObj.target.username + ']]',
|
||||
bodyLong: flagObj.description,
|
||||
path: '/uid/' + flagObj.targetId,
|
||||
|
||||
@@ -161,6 +161,7 @@ module.exports = function (Groups) {
|
||||
async.waterfall([
|
||||
async.apply(inviteOrRequestMembership, groupName, uid, 'invite'),
|
||||
async.apply(notifications.create, {
|
||||
type: 'group-invite',
|
||||
bodyShort: '[[groups:invited.notification_title, ' + groupName + ']]',
|
||||
bodyLong: '',
|
||||
nid: 'group:' + groupName + ':uid:' + uid + ':invite',
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
|
||||
var user = require('../user');
|
||||
var emailer = require('../emailer');
|
||||
var notifications = require('../notifications');
|
||||
var meta = require('../meta');
|
||||
var sockets = require('../socket.io');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
@@ -92,46 +89,6 @@ module.exports = function (Messaging) {
|
||||
if (notification) {
|
||||
notifications.push(notification, uids);
|
||||
}
|
||||
sendNotificationEmails(uids, messageObj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendNotificationEmails(uids, messageObj) {
|
||||
if (parseInt(meta.config.disableEmailSubscriptions, 10) === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
userData: function (next) {
|
||||
user.getUsersFields(uids, ['uid', 'username', 'userslug'], next);
|
||||
},
|
||||
userSettings: function (next) {
|
||||
user.getMultipleUserSettings(uids, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
|
||||
function (results, next) {
|
||||
results.userData = results.userData.filter(function (userData, index) {
|
||||
return userData && results.userSettings[index] && results.userSettings[index].sendChatNotifications;
|
||||
});
|
||||
async.each(results.userData, function (userData, next) {
|
||||
emailer.send('notif_chat', userData.uid, {
|
||||
subject: '[[email:notif.chat.subject, ' + messageObj.fromUser.username + ']]',
|
||||
summary: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]',
|
||||
message: messageObj,
|
||||
roomId: messageObj.roomId,
|
||||
username: userData.username,
|
||||
userslug: userData.userslug,
|
||||
}, next);
|
||||
}, next);
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
return winston.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ var meta = require('./meta');
|
||||
var batch = require('./batch');
|
||||
var plugins = require('./plugins');
|
||||
var utils = require('./utils');
|
||||
var emailer = require('./emailer');
|
||||
|
||||
var Notifications = module.exports;
|
||||
|
||||
@@ -178,9 +179,78 @@ Notifications.push = function (notification, uids, callback) {
|
||||
};
|
||||
|
||||
function pushToUids(uids, notification, callback) {
|
||||
var oneWeekAgo = Date.now() - 604800000;
|
||||
var unreadKeys = [];
|
||||
var readKeys = [];
|
||||
function sendNotification(uids, callback) {
|
||||
if (!uids.length) {
|
||||
return callback();
|
||||
}
|
||||
var oneWeekAgo = Date.now() - 604800000;
|
||||
var unreadKeys = [];
|
||||
var readKeys = [];
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
uids.forEach(function (uid) {
|
||||
unreadKeys.push('uid:' + uid + ':notifications:unread');
|
||||
readKeys.push('uid:' + uid + ':notifications:read');
|
||||
});
|
||||
|
||||
db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemove(readKeys, notification.nid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo, next);
|
||||
},
|
||||
function (next) {
|
||||
var websockets = require('./socket.io');
|
||||
if (websockets.server) {
|
||||
uids.forEach(function (uid) {
|
||||
websockets.in('uid_' + uid).emit('event:new_notification', notification);
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function sendEmail(uids, callback) {
|
||||
async.eachLimit(uids, 3, function (uid, next) {
|
||||
emailer.send('notification', uid, {
|
||||
path: notification.path,
|
||||
subject: '[' + (meta.config.title || 'NodeBB') + '] ' + notification.bodyShort,
|
||||
intro: '[[notifications:new_notification_from, ' + meta.config.title + ']]',
|
||||
body: notification.bodyLong || notification.bodyShort,
|
||||
showUnsubscribe: true,
|
||||
}, next);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function getUidsBySettings(uids, callback) {
|
||||
var uidsToNotify = [];
|
||||
var uidsToEmail = [];
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
User.getMultipleUserSettings(uids, next);
|
||||
},
|
||||
function (usersSettings, next) {
|
||||
usersSettings.forEach(function (userSettings) {
|
||||
var setting = userSettings['notificationType_' + notification.type] || 'notification';
|
||||
|
||||
if (setting === 'notification' || setting === 'notificationemail') {
|
||||
uidsToNotify.push(userSettings.uid);
|
||||
}
|
||||
|
||||
if (setting === 'email' || setting === 'notificationemail') {
|
||||
uidsToEmail.push(userSettings.uid);
|
||||
}
|
||||
});
|
||||
next(null, { uidsToNotify: uidsToNotify, uidsToEmail: uidsToEmail });
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
@@ -190,35 +260,32 @@ function pushToUids(uids, notification, callback) {
|
||||
if (!data || !data.notification || !data.uids || !data.uids.length) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
uids = data.uids;
|
||||
notification = data.notification;
|
||||
|
||||
uids.forEach(function (uid) {
|
||||
unreadKeys.push('uid:' + uid + ':notifications:unread');
|
||||
readKeys.push('uid:' + uid + ':notifications:read');
|
||||
});
|
||||
|
||||
db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemove(readKeys, notification.nid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo, next);
|
||||
},
|
||||
function (next) {
|
||||
var websockets = require('./socket.io');
|
||||
if (websockets.server) {
|
||||
uids.forEach(function (uid) {
|
||||
websockets.in('uid_' + uid).emit('event:new_notification', notification);
|
||||
});
|
||||
if (notification.type) {
|
||||
getUidsBySettings(data.uids, next);
|
||||
} else {
|
||||
next(null, { uidsToNotify: data.uids, uidsToEmail: [] });
|
||||
}
|
||||
|
||||
plugins.fireHook('action:notification.pushed', { notification: notification, uids: uids });
|
||||
},
|
||||
function (results, next) {
|
||||
async.parallel([
|
||||
function (next) {
|
||||
sendNotification(results.uidsToNotify, next);
|
||||
},
|
||||
function (next) {
|
||||
sendEmail(results.uidsToEmail, next);
|
||||
},
|
||||
], function (err) {
|
||||
next(err, results);
|
||||
});
|
||||
},
|
||||
function (results, next) {
|
||||
plugins.fireHook('action:notification.pushed', {
|
||||
notification: notification,
|
||||
uids: results.uidsToNotify,
|
||||
uidsNotified: results.uidsToNotify,
|
||||
uidsEmailed: results.uidsToEmail,
|
||||
});
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
|
||||
@@ -53,17 +53,25 @@ module.exports = function (Posts) {
|
||||
user.setUserField(data.uid, 'lastqueuetime', Date.now(), next);
|
||||
},
|
||||
function (next) {
|
||||
notifications.create({
|
||||
nid: 'post-queued-' + id,
|
||||
mergeId: 'post-queue',
|
||||
bodyShort: '[[notifications:post_awaiting_review]]',
|
||||
bodyLong: data.content,
|
||||
path: '/post-queue',
|
||||
async.parallel({
|
||||
notification: function (next) {
|
||||
notifications.create({
|
||||
type: 'post-queue',
|
||||
nid: 'post-queue-' + id,
|
||||
mergeId: 'post-queue',
|
||||
bodyShort: '[[notifications:post_awaiting_review]]',
|
||||
bodyLong: data.content,
|
||||
path: '/post-queue',
|
||||
}, next);
|
||||
},
|
||||
cid: function (next) {
|
||||
getCid(type, data, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (notification, next) {
|
||||
if (notification) {
|
||||
notifications.pushGroups(notification, ['administrators', 'Global Moderators'], next);
|
||||
function (results, next) {
|
||||
if (results.notification) {
|
||||
notifications.pushGroups(results.notification, ['administrators', 'Global Moderators', 'cid:' + results.cid + ':privileges:moderate'], next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
@@ -79,20 +87,26 @@ module.exports = function (Posts) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function getCid(type, data, callback) {
|
||||
if (type === 'topic') {
|
||||
return setImmediate(callback, null, data.cid);
|
||||
} else if (type === 'reply') {
|
||||
topics.getTopicField(data.tid, 'cid', callback);
|
||||
} else {
|
||||
return setImmediate(callback, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
function canPost(type, data, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
if (type === 'topic') {
|
||||
next(null, data.cid);
|
||||
} else if (type === 'reply') {
|
||||
topics.getTopicField(data.tid, 'cid', next);
|
||||
}
|
||||
getCid(type, data, next);
|
||||
},
|
||||
function (cid, next) {
|
||||
async.parallel({
|
||||
canPost: function (next) {
|
||||
if (type === 'topic') {
|
||||
privileges.categories.can('topics:create', data.cid, data.uid, next);
|
||||
privileges.categories.can('topics:create', cid, data.uid, next);
|
||||
} else if (type === 'reply') {
|
||||
privileges.categories.can('topics:reply', cid, data.uid, next);
|
||||
}
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
var posts = require('../posts');
|
||||
var notifications = require('../notifications');
|
||||
var privileges = require('../privileges');
|
||||
var meta = require('../meta');
|
||||
var emailer = require('../emailer');
|
||||
var plugins = require('../plugins');
|
||||
var utils = require('../utils');
|
||||
|
||||
@@ -239,36 +235,6 @@ module.exports = function (Topics) {
|
||||
notifications.push(notification, followers);
|
||||
}
|
||||
|
||||
if (parseInt(meta.config.disableEmailSubscriptions, 10) === 1) {
|
||||
return next();
|
||||
}
|
||||
|
||||
async.eachLimit(followers, 3, function (toUid, next) {
|
||||
async.parallel({
|
||||
userData: async.apply(user.getUserFields, toUid, ['username', 'userslug']),
|
||||
userSettings: async.apply(user.getSettings, toUid),
|
||||
}, function (err, data) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (data.userSettings.sendPostNotifications) {
|
||||
emailer.send('notif_post', toUid, {
|
||||
pid: postData.pid,
|
||||
subject: '[' + (meta.config.title || 'NodeBB') + '] ' + title,
|
||||
intro: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
|
||||
postBody: postData.content.replace(/"\/\//g, '"https://'),
|
||||
username: data.userData.username,
|
||||
userslug: data.userData.userslug,
|
||||
topicSlug: postData.topic.slug,
|
||||
showUnsubscribe: true,
|
||||
}, next);
|
||||
} else {
|
||||
winston.debug('[topics.notifyFollowers] uid ' + toUid + ' does not have post notifications enabled, skipping.');
|
||||
next();
|
||||
}
|
||||
});
|
||||
});
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
|
||||
48
src/upgrades/1.7.1/notification-settings.js
Normal file
48
src/upgrades/1.7.1/notification-settings.js
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var batch = require('../../batch');
|
||||
var db = require('../../database');
|
||||
|
||||
module.exports = {
|
||||
name: 'Convert old notification digest settings',
|
||||
timestamp: Date.UTC(2017, 10, 15),
|
||||
method: function (callback) {
|
||||
var progress = this.progress;
|
||||
|
||||
batch.processSortedSet('users:joindate', function (uids, next) {
|
||||
async.eachLimit(uids, 500, function (uid, next) {
|
||||
progress.incr();
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getObjectFields('user:' + uid + ':settings', ['sendChatNotifications', 'sendPostNotifications'], next);
|
||||
},
|
||||
function (userSettings, _next) {
|
||||
if (!userSettings) {
|
||||
return next();
|
||||
}
|
||||
var tasks = [];
|
||||
if (parseInt(userSettings.sendChatNotifications, 10) === 1) {
|
||||
tasks.push(async.apply(db.setObjectField, 'user:' + uid + ':settings', 'notificationType_new-chat', 'notificationemail'));
|
||||
}
|
||||
if (parseInt(userSettings.sendPostNotifications, 10) === 1) {
|
||||
tasks.push(async.apply(db.setObjectField, 'user:' + uid + ':settings', 'notificationType_new-reply', 'notificationemail'));
|
||||
}
|
||||
if (!tasks.length) {
|
||||
return next();
|
||||
}
|
||||
|
||||
async.series(tasks, function (err) {
|
||||
_next(err);
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
db.deleteObjectFields('user:' + uid + ':settings', ['sendChatNotifications', 'sendPostNotifications'], next);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
}, {
|
||||
progress: progress,
|
||||
}, callback);
|
||||
},
|
||||
};
|
||||
16
src/user.js
16
src/user.js
@@ -208,13 +208,17 @@ User.isGlobalModerator = function (uid, callback) {
|
||||
privileges.users.isGlobalModerator(uid, callback);
|
||||
};
|
||||
|
||||
User.getPrivileges = function (uid, callback) {
|
||||
async.parallel({
|
||||
isAdmin: async.apply(User.isAdministrator, uid),
|
||||
isGlobalModerator: async.apply(User.isGlobalModerator, uid),
|
||||
isModeratorOfAnyCategory: async.apply(User.isModeratorOfAnyCategory, uid),
|
||||
}, callback);
|
||||
};
|
||||
|
||||
User.isPrivileged = function (uid, callback) {
|
||||
async.parallel([
|
||||
async.apply(User.isAdministrator, uid),
|
||||
async.apply(User.isGlobalModerator, uid),
|
||||
async.apply(User.isModeratorOfAnyCategory, uid),
|
||||
], function (err, results) {
|
||||
callback(err, results ? results.some(Boolean) : false);
|
||||
User.getPrivileges(uid, function (err, results) {
|
||||
callback(err, results ? (results.isAdmin || results.isGlobalModerator || results.isModeratorOfAnyCategory) : false);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ module.exports = function (User) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
notifications.create({
|
||||
type: 'new-register',
|
||||
bodyShort: '[[notifications:new_register, ' + username + ']]',
|
||||
nid: 'new_register:' + username,
|
||||
path: '/admin/manage/registration',
|
||||
|
||||
@@ -131,6 +131,12 @@ module.exports = function (User) {
|
||||
notificationSound: data.notificationSound,
|
||||
incomingChatSound: data.incomingChatSound,
|
||||
outgoingChatSound: data.outgoingChatSound,
|
||||
notificationType_upvote: data.notificationType_upvote,
|
||||
'notificationType_new-topic': data['notificationType_new-topic'],
|
||||
'notificationType_new-reply': data['notificationType_new-reply'],
|
||||
notificationType_follow: data.notificationType_follow,
|
||||
'notificationType_new-chat': data['notificationType_new-chat'],
|
||||
'notificationType_group-invite': data['notificationType_group-invite'],
|
||||
};
|
||||
|
||||
if (data.bootswatchSkin) {
|
||||
|
||||
57
src/views/emails/notification.tpl
Normal file
57
src/views/emails/notification.tpl
Normal file
@@ -0,0 +1,57 @@
|
||||
<!-- IMPORT emails/partials/header.tpl -->
|
||||
|
||||
<!-- Email Body : BEGIN -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||
|
||||
<!-- Hero Image, Flush : BEGIN -->
|
||||
<tr>
|
||||
<td bgcolor="#efeff0" style="text-align: center; background-image: url({url}/assets/images/emails/triangularbackground.png); background-size: cover; background-repeat: no-repeat;">
|
||||
<img src="{url}/assets/images/emails/unreadpost.png" width="300" height="300" border="0" align="center" style="width: 300px; height: 300px; max-width: 300px; height: auto; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;" class="g-img">
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Hero Image, Flush : END -->
|
||||
|
||||
<!-- 1 Column Text + Button : BEGIN -->
|
||||
<tr>
|
||||
<td bgcolor="#efeff0">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td style="padding: 40px 40px 0px 40px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;">
|
||||
<p style="margin: 0 0 20px 0;">{intro}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0px 60px 40px 60px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;">
|
||||
{body}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0 40px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;">
|
||||
<!-- Button : BEGIN -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: auto;">
|
||||
<tr>
|
||||
<td style="border-radius: 3px; background: #222222; text-align: center;" class="button-td">
|
||||
<a href="{url}{path}" style="background: #222222; border: 15px solid #222222; font-family: sans-serif; font-size: 13px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 3px; font-weight: bold;" class="button-a">
|
||||
<span style="color:#ffffff;" class="button-link"> [[email:notif.cta]] </span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- Button : END -->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 40px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;">
|
||||
<h2 style="margin: 0 0 10px 0; font-family: sans-serif; font-size: 18px; line-height: 21px; color: #333333; font-weight: bold;">[[email:closing]]</h2>
|
||||
<p style="margin: 0;">{site_title}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 1 Column Text + Button : END -->
|
||||
|
||||
</table>
|
||||
<!-- Email Body : END -->
|
||||
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
Reference in New Issue
Block a user