feat: email notification (#26692)

update email sending flow for inviting users to workspace and forgot
password with new templates

fixes: https://github.com/appsmithorg/appsmith/issues/19462

code changes:

**new code**
* introduces new EmailService

**refactoring**
* moves all email sending flows to the new email service
* moves all email constants and templates to the Email constants

**code deleted**
* removed code for welcome email invite
This commit is contained in:
Shubham Saxena 2023-09-17 17:00:34 +05:30 committed by GitHub
parent 9b46e7e127
commit feaf5eea03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 3549 additions and 1419 deletions

View File

@ -214,10 +214,9 @@ public class AuthenticationSuccessHandlerCE implements ServerAuthenticationSucce
.flatMap(defaultApplication -> postVerificationRequiredHandler(
webFilterExchange, user, defaultApplication, TRUE));
} else {
return Mono.zip(
userService.sendWelcomeEmail(user, originHeader),
createDefaultApplication(defaultWorkspaceId, authentication))
.flatMap(obj -> redirectHelper.handleRedirect(webFilterExchange, obj.getT2(), true));
return createDefaultApplication(defaultWorkspaceId, authentication)
.flatMap(application ->
redirectHelper.handleRedirect(webFilterExchange, application, true));
}
});
} else {
@ -226,9 +225,7 @@ public class AuthenticationSuccessHandlerCE implements ServerAuthenticationSucce
if (TRUE.equals(isVerificationRequired)) {
return postVerificationRequiredHandler(webFilterExchange, user, null, TRUE);
} else {
return userService
.sendWelcomeEmail(user, originHeader)
.then(redirectHelper.handleRedirect(webFilterExchange, null, true));
return redirectHelper.handleRedirect(webFilterExchange, null, true);
}
});
}
@ -289,17 +286,12 @@ public class AuthenticationSuccessHandlerCE implements ServerAuthenticationSucce
redirectionMono = workspaceService
.isCreateWorkspaceAllowed(TRUE)
.flatMap(isCreateWorkspaceAllowed -> {
if (isCreateWorkspaceAllowed) {
return Mono.zip(
userService.sendWelcomeEmail(user, originHeader),
createDefaultApplication(defaultWorkspaceId, authentication))
.flatMap(objects -> handleOAuth2Redirect(
webFilterExchange, objects.getT2(), finalIsFromSignup));
if (isCreateWorkspaceAllowed.equals(Boolean.TRUE)) {
return createDefaultApplication(defaultWorkspaceId, authentication)
.flatMap(application -> handleOAuth2Redirect(
webFilterExchange, application, finalIsFromSignup));
}
return userService
.sendWelcomeEmail(user, originHeader)
.then(handleOAuth2Redirect(webFilterExchange, null, finalIsFromSignup));
return handleOAuth2Redirect(webFilterExchange, null, finalIsFromSignup);
});
} else {
redirectionMono = handleOAuth2Redirect(webFilterExchange, null, isFromSignup);

View File

@ -0,0 +1,32 @@
package com.appsmith.server.constants.ce;
import com.appsmith.server.constants.FieldName;
public class EmailConstantsCE {
public static final String INSTANCE_NAME = "instanceName";
public static final String RESET_URL = "resetUrl";
public static final String EMAIL_ROLE_ADMINISTRATOR_TEXT = "an " + FieldName.ADMINISTRATOR.toLowerCase();
public static final String EMAIL_ROLE_DEVELOPER_TEXT = "a " + FieldName.DEVELOPER.toLowerCase();
public static final String EMAIL_ROLE_VIEWER_TEXT = "an " + FieldName.VIEWER.toLowerCase();
public static final String PRIMARY_LINK_URL = "primaryLinkUrl";
public static final String PRIMARY_LINK_TEXT = "primaryLinkText";
public static final String PRIMARY_LINK_TEXT_USER_SIGNUP = "Sign up now";
public static final String PRIMARY_LINK_TEXT_INVITE_TO_INSTANCE = "Go to instance";
public static final String PRIMARY_LINK_TEXT_WORKSPACE_REDIRECTION = "Go to your Appsmith workspace";
public static final String INVITE_USER_CLIENT_URL_FORMAT = "%s/user/signup?email=%s";
public static final String WORKSPACE_EMAIL_SUBJECT_FOR_NEW_USER = "Youre invited to the workspace %s.";
public static final String FORGOT_PASSWORD_EMAIL_SUBJECT = "Reset your Appsmith password";
public static final String EMAIL_VERIFICATION_EMAIL_SUBJECT = "Verify your account";
public static final String INSTANCE_ADMIN_INVITE_EMAIL_SUBJECT = "You're invited to an Appsmith instance";
public static final String INVITE_WORKSPACE_TEMPLATE_EXISTING_USER_CE =
"email/ce/inviteWorkspaceExistingUserTemplate.html";
public static final String INVITE_WORKSPACE_TEMPLATE_NEW_USER_CE = "email/ce/inviteWorkspaceNewUserTemplate.html";
public static final String FORGOT_PASSWORD_TEMPLATE_CE = "email/ce/forgotPasswordTemplate.html";
public static final String INSTANCE_ADMIN_INVITE_EMAIL_TEMPLATE = "email/ce/instanceAdminInviteTemplate.html";
public static final String EMAIL_VERIFICATION_EMAIL_TEMPLATE = "email/emailVerificationTemplate.html";
public static final String WORKSPACE_URL = "%s/applications#%s";
public static final String INVITER_FIRST_NAME = "inviterFirstName";
public static final String INVITER_WORKSPACE_NAME = "inviterWorkspaceName";
public static final String EMAIL_VERIFICATION_URL = "verificationUrl";
}

View File

@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@ -47,19 +48,23 @@ public class InstanceAdminControllerCE {
@PutMapping(
value = "/env",
consumes = {MediaType.APPLICATION_JSON_VALUE})
public Mono<ResponseDTO<Void>> saveEnvChangesJSON(@Valid @RequestBody Map<String, String> changes) {
public Mono<ResponseDTO<Void>> saveEnvChangesJSON(
@Valid @RequestBody Map<String, String> changes, @RequestHeader("Origin") String originHeader) {
log.debug("Applying env updates {}", changes.keySet());
return envManager.applyChanges(changes).thenReturn(new ResponseDTO<>(HttpStatus.OK.value(), null, null));
return envManager
.applyChanges(changes, originHeader)
.thenReturn(new ResponseDTO<>(HttpStatus.OK.value(), null, null));
}
@JsonView(Views.Public.class)
@PutMapping(
value = "/env",
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public Mono<ResponseDTO<Void>> saveEnvChangesMultipartFormData(ServerWebExchange exchange) {
public Mono<ResponseDTO<Void>> saveEnvChangesMultipartFormData(
@RequestHeader("Origin") String originHeader, ServerWebExchange exchange) {
log.debug("Applying env updates from form data");
return exchange.getMultipartData()
.flatMap(envManager::applyChangesFromMultipartFormData)
.flatMap(formData -> envManager.applyChangesFromMultipartFormData(formData, originHeader))
.thenReturn(new ResponseDTO<>(HttpStatus.OK.value(), null, null));
}

View File

@ -91,9 +91,11 @@ public class UserControllerCE extends BaseController<UserService, User, String>
value = "/super",
consumes = {MediaType.APPLICATION_JSON_VALUE})
public Mono<ResponseDTO<User>> createSuperUser(
@Valid @RequestBody UserSignupRequestDTO resource, ServerWebExchange exchange) {
@Valid @RequestBody UserSignupRequestDTO resource,
@RequestHeader("Origin") String originHeader,
ServerWebExchange exchange) {
return userSignup
.signupAndLoginSuper(resource, exchange)
.signupAndLoginSuper(resource, originHeader, exchange)
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
}
@ -101,8 +103,9 @@ public class UserControllerCE extends BaseController<UserService, User, String>
@PostMapping(
value = "/super",
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public Mono<Void> createSuperUserFromFormData(ServerWebExchange exchange) {
return userSignup.signupAndLoginSuperFromFormData(exchange);
public Mono<Void> createSuperUserFromFormData(
@RequestHeader("Origin") String originHeader, ServerWebExchange exchange) {
return userSignup.signupAndLoginSuperFromFormData(originHeader, exchange);
}
@JsonView(Views.Public.class)

View File

@ -0,0 +1,5 @@
package com.appsmith.server.services;
import com.appsmith.server.services.ce.EmailServiceCE;
public interface EmailService extends EmailServiceCE {}

View File

@ -0,0 +1,12 @@
package com.appsmith.server.services;
import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.services.ce.EmailServiceCEImpl;
import org.springframework.stereotype.Service;
@Service
public class EmailServiceImpl extends EmailServiceCEImpl implements EmailService {
public EmailServiceImpl(EmailSender emailSender, TenantService tenantService) {
super(emailSender, tenantService);
}
}

View File

@ -3,7 +3,6 @@ package com.appsmith.server.services;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.configurations.EmailConfig;
import com.appsmith.server.helpers.RedirectHelper;
import com.appsmith.server.helpers.UserUtils;
import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.ratelimiting.RateLimitService;
@ -49,7 +48,7 @@ public class UserServiceImpl extends UserServiceCECompatibleImpl implements User
PermissionGroupService permissionGroupService,
UserUtils userUtils,
EmailVerificationTokenRepository emailVerificationTokenRepository,
RedirectHelper redirectHelper,
EmailService emailService,
RateLimitService rateLimitService) {
super(
scheduler,
@ -74,7 +73,7 @@ public class UserServiceImpl extends UserServiceCECompatibleImpl implements User
permissionGroupService,
userUtils,
emailVerificationTokenRepository,
redirectHelper,
emailService,
rateLimitService);
}
}

View File

@ -0,0 +1,23 @@
package com.appsmith.server.services.ce;
import com.appsmith.server.domains.PermissionGroup;
import com.appsmith.server.domains.User;
import com.appsmith.server.domains.Workspace;
import reactor.core.publisher.Mono;
public interface EmailServiceCE {
Mono<Boolean> sendForgotPasswordEmail(String email, String resetUrl, String originHeader);
Mono<Boolean> sendInviteUserToWorkspaceEmail(
User invitingUser,
User invitedUser,
Workspace workspaceInvitedTo,
PermissionGroup assignedPermissionGroup,
String originHeader,
boolean isNewUser);
Mono<Boolean> sendEmailVerificationEmail(User user, String verificationUrl);
Mono<Boolean> sendInstanceAdminInviteEmail(
User invitedUser, User invitingUser, String originHeader, boolean isNewUser);
}

View File

@ -0,0 +1,159 @@
package com.appsmith.server.services.ce;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.PermissionGroup;
import com.appsmith.server.domains.TenantConfiguration;
import com.appsmith.server.domains.User;
import com.appsmith.server.domains.Workspace;
import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.services.TenantService;
import org.apache.commons.lang3.StringUtils;
import reactor.core.publisher.Mono;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import static com.appsmith.server.constants.ce.EmailConstantsCE.*;
public class EmailServiceCEImpl implements EmailServiceCE {
private final EmailSender emailSender;
private final TenantService tenantService;
public EmailServiceCEImpl(EmailSender emailSender, TenantService tenantService) {
this.emailSender = emailSender;
this.tenantService = tenantService;
}
@Override
public Mono<Boolean> sendForgotPasswordEmail(String email, String resetUrl, String originHeader) {
Map<String, String> params = new HashMap<>();
params.put(RESET_URL, resetUrl);
return this.enrichParams(params)
.flatMap(enrichedParams -> this.enrichWithBrandParams(enrichedParams, originHeader)
.flatMap(updatedParams -> emailSender.sendMail(
email,
String.format(FORGOT_PASSWORD_EMAIL_SUBJECT, updatedParams.get(INSTANCE_NAME)),
getForgotPasswordTemplate(),
updatedParams)));
}
@Override
public Mono<Boolean> sendInviteUserToWorkspaceEmail(
User invitingUser,
User invitedUser,
Workspace workspaceInvitedTo,
PermissionGroup assignedPermissionGroup,
String originHeader,
boolean isNewUser) {
String inviteUrl = isNewUser
? String.format(
INVITE_USER_CLIENT_URL_FORMAT,
originHeader,
URLEncoder.encode(invitedUser.getUsername().toLowerCase(), StandardCharsets.UTF_8))
: originHeader;
String emailSubject = String.format(WORKSPACE_EMAIL_SUBJECT_FOR_NEW_USER, workspaceInvitedTo.getName());
Map<String, String> params = getInviteToWorkspaceEmailParams(
workspaceInvitedTo, invitingUser, inviteUrl, assignedPermissionGroup.getName(), isNewUser);
return this.enrichParams(params).flatMap(enrichedParams -> this.enrichWithBrandParams(
enrichedParams, originHeader)
.flatMap(updatedParams -> emailSender.sendMail(
invitedUser.getEmail(), emailSubject, getWorkspaceInviteTemplate(isNewUser), updatedParams)));
}
@Override
public Mono<Boolean> sendEmailVerificationEmail(User user, String verificationURL) {
Map<String, String> params = new HashMap<>();
params.put(EMAIL_VERIFICATION_URL, verificationURL);
return this.enrichParams(params)
.flatMap(updatedParams -> emailSender.sendMail(
user.getEmail(),
EMAIL_VERIFICATION_EMAIL_SUBJECT,
EMAIL_VERIFICATION_EMAIL_TEMPLATE,
updatedParams));
}
@Override
public Mono<Boolean> sendInstanceAdminInviteEmail(
User invitedUser, User invitingUser, String originHeader, boolean isNewUser) {
Map<String, String> params = new HashMap<>();
String inviteUrl = isNewUser
? String.format(
INVITE_USER_CLIENT_URL_FORMAT,
originHeader,
URLEncoder.encode(invitedUser.getUsername().toLowerCase(), StandardCharsets.UTF_8))
: originHeader;
params.put(PRIMARY_LINK_URL, inviteUrl);
String primaryLinkText = isNewUser ? PRIMARY_LINK_TEXT_USER_SIGNUP : PRIMARY_LINK_TEXT_INVITE_TO_INSTANCE;
params.put(PRIMARY_LINK_TEXT, primaryLinkText);
if (invitingUser != null) {
params.put(INVITER_FIRST_NAME, StringUtils.defaultIfEmpty(invitingUser.getName(), invitingUser.getEmail()));
}
return this.enrichParams(params)
.flatMap(enrichedParams -> this.enrichWithBrandParams(enrichedParams, originHeader))
.flatMap(updatedParams -> emailSender.sendMail(
invitedUser.getEmail(),
String.format(INSTANCE_ADMIN_INVITE_EMAIL_SUBJECT),
getAdminInstanceInviteTemplate(),
updatedParams));
}
protected Mono<Map<String, String>> enrichParams(Map<String, String> params) {
return tenantService.getDefaultTenant().map(tenant -> {
final TenantConfiguration tenantConfiguration = tenant.getTenantConfiguration();
params.put(INSTANCE_NAME, StringUtils.defaultIfEmpty(tenantConfiguration.getInstanceName(), "Appsmith"));
return params;
});
}
protected Mono<Map<String, String>> enrichWithBrandParams(Map<String, String> params, String origin) {
return Mono.just(params);
}
protected String getForgotPasswordTemplate() {
return FORGOT_PASSWORD_TEMPLATE_CE;
}
protected String getWorkspaceInviteTemplate(boolean isNewUser) {
if (isNewUser) return INVITE_WORKSPACE_TEMPLATE_NEW_USER_CE;
return INVITE_WORKSPACE_TEMPLATE_EXISTING_USER_CE;
}
protected String getAdminInstanceInviteTemplate() {
return INSTANCE_ADMIN_INVITE_EMAIL_TEMPLATE;
}
private Map<String, String> getInviteToWorkspaceEmailParams(
Workspace workspace, User inviter, String inviteUrl, String roleType, boolean isNewUser) {
Map<String, String> params = new HashMap<>();
if (workspace != null) {
params.put(INVITER_WORKSPACE_NAME, workspace.getName());
}
if (inviter != null) {
params.put(INVITER_FIRST_NAME, StringUtils.defaultIfEmpty(inviter.getName(), inviter.getEmail()));
}
if (roleType != null) {
if (roleType.startsWith(FieldName.ADMINISTRATOR)) {
params.put(FieldName.ROLE, EMAIL_ROLE_ADMINISTRATOR_TEXT);
} else if (roleType.startsWith(FieldName.DEVELOPER)) {
params.put(FieldName.ROLE, EMAIL_ROLE_DEVELOPER_TEXT);
} else if (roleType.startsWith(FieldName.VIEWER)) {
params.put(FieldName.ROLE, EMAIL_ROLE_VIEWER_TEXT);
}
}
if (isNewUser) {
params.put(PRIMARY_LINK_URL, inviteUrl);
params.put(PRIMARY_LINK_TEXT, PRIMARY_LINK_TEXT_USER_SIGNUP);
} else {
if (workspace != null) {
params.put(PRIMARY_LINK_URL, String.format(WORKSPACE_URL, inviteUrl, workspace.getId()));
}
params.put(PRIMARY_LINK_TEXT, PRIMARY_LINK_TEXT_WORKSPACE_REDIRECTION);
}
return params;
}
}

View File

@ -2,7 +2,6 @@ package com.appsmith.server.services.ce;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.User;
import com.appsmith.server.domains.Workspace;
import com.appsmith.server.dtos.ResendEmailVerificationDTO;
import com.appsmith.server.dtos.ResetUserPasswordDTO;
import com.appsmith.server.dtos.UserProfileDTO;
@ -13,7 +12,6 @@ import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Map;
import java.util.Set;
public interface UserServiceCE extends CrudService<User, String> {
@ -30,29 +28,18 @@ public interface UserServiceCE extends CrudService<User, String> {
Mono<Boolean> resetPasswordAfterForgotPassword(String token, User user);
Mono<UserSignupDTO> createUser(User user, String originHeader);
Mono<UserSignupDTO> createUserAndSendEmail(User user, String originHeader);
Mono<UserSignupDTO> createUser(User user);
Mono<User> userCreate(User user, boolean isAdminUser);
Mono<? extends User> createNewUserAndSendInviteEmail(
String email, String originHeader, Workspace workspace, User inviter, String role);
Mono<User> updateCurrentUser(UserUpdateDTO updates, ServerWebExchange exchange);
Map<String, String> getEmailParams(Workspace workspace, User inviterUser, String inviteUrl, boolean isNewUser);
Mono<Boolean> isUsersEmpty();
Mono<UserProfileDTO> buildUserProfileDTO(User user);
Flux<User> getAllByEmails(Set<String> emails, AclPermission permission);
Mono<Map<String, String>> updateTenantLogoInParams(Map<String, String> params, String origin);
Mono<User> sendWelcomeEmail(User user, String originHeader);
Mono<User> updateWithoutPermission(String id, User update);
Mono<Boolean> resendEmailVerification(ResendEmailVerificationDTO resendEmailVerificationDTO, String redirectUrl);

View File

@ -6,7 +6,6 @@ import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.configurations.EmailConfig;
import com.appsmith.server.constants.Appsmith;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.constants.RateLimitConstants;
import com.appsmith.server.domains.EmailVerificationToken;
@ -27,7 +26,6 @@ import com.appsmith.server.dtos.UserSignupDTO;
import com.appsmith.server.dtos.UserUpdateDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.RedirectHelper;
import com.appsmith.server.helpers.UserUtils;
import com.appsmith.server.helpers.ValidationUtils;
import com.appsmith.server.notifications.EmailSender;
@ -38,6 +36,7 @@ import com.appsmith.server.repositories.PasswordResetTokenRepository;
import com.appsmith.server.repositories.UserRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.BaseService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.TenantService;
@ -68,7 +67,6 @@ import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebSession;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
@ -106,34 +104,26 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
private final PasswordResetTokenRepository passwordResetTokenRepository;
private final PasswordEncoder passwordEncoder;
private final EmailSender emailSender;
private final ApplicationRepository applicationRepository;
private final PolicySolution policySolution;
private final CommonConfig commonConfig;
private final EmailConfig emailConfig;
private final UserChangedHandler userChangedHandler;
private final EncryptionService encryptionService;
private final UserDataService userDataService;
private final TenantService tenantService;
private final PermissionGroupService permissionGroupService;
private final UserUtils userUtils;
private final EmailService emailService;
private final RateLimitService rateLimitService;
private final RedirectHelper redirectHelper;
private static final WebFilterChain EMPTY_WEB_FILTER_CHAIN = serverWebExchange -> Mono.empty();
private static final String WELCOME_USER_EMAIL_TEMPLATE = "email/welcomeUserTemplate.html";
private static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "email/forgotPasswordTemplate.html";
private static final String FORGOT_PASSWORD_CLIENT_URL_FORMAT = "%s/user/resetPassword?token=%s";
private static final String INVITE_USER_CLIENT_URL_FORMAT = "%s/user/signup?email=%s";
public static final String INVITE_USER_EMAIL_TEMPLATE = "email/inviteUserTemplate.html";
private static final Pattern ALLOWED_ACCENTED_CHARACTERS_PATTERN = Pattern.compile("^[\\p{L} 0-9 .\'\\-]+$");
private static final String EMAIL_VERIFICATION_CLIENT_URL_FORMAT =
"%s/user/verify?token=%s&email=%s&redirectUrl=%s";
private static final String EMAIL_VERIFICATION_ERROR_URL_FORMAT = "/user/verify-error?code=%s&message=%s&email=%s";
private static final String USER_EMAIL_VERIFICATION_EMAIL_TEMPLATE = "email/emailVerificationTemplate.html";
private final EmailVerificationTokenRepository emailVerificationTokenRepository;
private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
@ -162,18 +152,16 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
PermissionGroupService permissionGroupService,
UserUtils userUtils,
EmailVerificationTokenRepository emailVerificationTokenRepository,
RedirectHelper redirectHelper,
EmailService emailService,
RateLimitService rateLimitService) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
this.workspaceService = workspaceService;
this.sessionUserService = sessionUserService;
this.passwordResetTokenRepository = passwordResetTokenRepository;
this.passwordEncoder = passwordEncoder;
this.emailSender = emailSender;
this.applicationRepository = applicationRepository;
this.policySolution = policySolution;
this.commonConfig = commonConfig;
this.emailConfig = emailConfig;
this.userChangedHandler = userChangedHandler;
this.encryptionService = encryptionService;
this.userDataService = userDataService;
@ -182,7 +170,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
this.userUtils = userUtils;
this.rateLimitService = rateLimitService;
this.emailVerificationTokenRepository = emailVerificationTokenRepository;
this.redirectHelper = redirectHelper;
this.emailService = emailService;
}
@Override
@ -302,12 +290,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
log.debug("Password reset url for email: {}: {}", passwordResetToken.getEmail(), resetUrl);
Map<String, String> params = new HashMap<>();
params.put("resetUrl", resetUrl);
return updateTenantLogoInParams(params, resetUserPasswordDTO.getBaseUrl())
.flatMap(updatedParams -> emailSender.sendMail(
email, "Appsmith Password Reset", FORGOT_PASSWORD_EMAIL_TEMPLATE, updatedParams));
return emailService.sendForgotPasswordEmail(email, resetUrl, resetUserPasswordDTO.getBaseUrl());
})
.thenReturn(true);
}
@ -446,7 +429,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
@Override
public Mono<User> create(User user) {
// This is the path that is taken when a new user signs up on its own
return createUser(user, null).map(UserSignupDTO::getUser);
return createUser(user).map(UserSignupDTO::getUser);
}
@Override
@ -510,12 +493,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
}
@Override
public Mono<UserSignupDTO> createUser(User user, String originHeader) {
if (originHeader == null || originHeader.isBlank()) {
// Default to the production link
originHeader = Appsmith.DEFAULT_ORIGIN_HEADER;
}
public Mono<UserSignupDTO> createUser(User user) {
// Only encode the password if it's a form signup. For OAuth signups, we don't need password
if (LoginSource.FORM.equals(user.getSource())) {
if (user.getPassword() == null || user.getPassword().isBlank()) {
@ -597,17 +575,6 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
* @param user User object representing the user to be created/enabled.
* @return Publishes the user object, after having been saved.
*/
@Override
public Mono<UserSignupDTO> createUserAndSendEmail(User user, String originHeader) {
return createUser(user, originHeader).flatMap(userSignupDTO -> {
User savedUser = userSignupDTO.getUser();
Mono<User> userMono = emailConfig.isWelcomeEmailEnabled()
? sendWelcomeEmail(savedUser, originHeader)
: Mono.just(savedUser);
return userMono.thenReturn(userSignupDTO);
});
}
private Mono<User> signupIfAllowed(User user) {
boolean isAdminUser = false;
@ -642,30 +609,6 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
});
}
@Override
public Mono<User> sendWelcomeEmail(User user, String originHeader) {
Map<String, String> params = new HashMap<>();
params.put("primaryLinkUrl", originHeader);
return updateTenantLogoInParams(params, originHeader)
.flatMap(updatedParams -> emailSender.sendMail(
user.getEmail(), "Welcome to Appsmith", WELCOME_USER_EMAIL_TEMPLATE, updatedParams))
.elapsed()
.map(pair -> {
log.debug("UserServiceCEImpl::Time taken to send email: {} ms", pair.getT1());
return pair.getT2();
})
.onErrorResume(error -> {
// Swallowing this exception because we don't want this to affect the rest of the flow.
log.error(
"Ignoring error: Unable to send welcome email to the user {}. Cause: ",
user.getEmail(),
Exceptions.unwrap(error));
return Mono.just(TRUE);
})
.thenReturn(user);
}
@Override
public Mono<User> update(String id, User userUpdate) {
Mono<User> userFromRepository = repository
@ -702,41 +645,6 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
return repository.save(existingUser).map(userChangedHandler::publish);
}
@Override
public Mono<? extends User> createNewUserAndSendInviteEmail(
String email, String originHeader, Workspace workspace, User inviter, String role) {
User newUser = new User();
newUser.setEmail(email.toLowerCase());
// This is a new user. Till the user signs up, this user would be disabled.
newUser.setIsEnabled(false);
// The invite token is not used today and doesn't need to be verified. We still save the invite token with the
// role information to classify the user persona.
newUser.setInviteToken(role + ":" + UUID.randomUUID());
boolean isAdminUser = commonConfig.getAdminEmails().contains(email.toLowerCase());
// Call user service's userCreate function so that the default workspace, etc are also created along with
// assigning basic permissions.
return userCreate(newUser, isAdminUser).flatMap(createdUser -> {
log.debug("Going to send email for invite user to {}", createdUser.getEmail());
String inviteUrl = String.format(
INVITE_USER_CLIENT_URL_FORMAT,
originHeader,
URLEncoder.encode(createdUser.getEmail(), StandardCharsets.UTF_8));
// Email template parameters initialization below.
Map<String, String> params = getEmailParams(workspace, inviter, inviteUrl, true);
// We have sent out the emails. Just send back the saved user.
return updateTenantLogoInParams(params, originHeader)
.flatMap(updatedParams -> emailSender.sendMail(
createdUser.getEmail(), "Invite for Appsmith", INVITE_USER_EMAIL_TEMPLATE, updatedParams))
.thenReturn(createdUser);
});
}
@Override
public Flux<User> get(MultiValueMap<String, String> params) {
// Get All Users should not be supported. Return an error
@ -806,30 +714,6 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
});
}
@Override
public Map<String, String> getEmailParams(Workspace workspace, User inviter, String inviteUrl, boolean isNewUser) {
Map<String, String> params = new HashMap<>();
if (inviter != null) {
params.put(
"inviterFirstName",
org.apache.commons.lang3.StringUtils.defaultIfEmpty(inviter.getName(), inviter.getEmail()));
}
if (workspace != null) {
params.put("inviterWorkspaceName", workspace.getName());
}
if (isNewUser) {
params.put("primaryLinkUrl", inviteUrl);
params.put("primaryLinkText", "Sign up now");
} else {
if (workspace != null) {
params.put("primaryLinkUrl", inviteUrl + "/applications#" + workspace.getId());
}
params.put("primaryLinkText", "Go to workspace");
}
return params;
}
@Override
public Mono<Boolean> isUsersEmpty() {
return repository.isUsersEmpty();
@ -892,11 +776,6 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
return repository.findAllByEmails(emails);
}
@Override
public Mono<Map<String, String>> updateTenantLogoInParams(Map<String, String> params, String origin) {
return Mono.just(params);
}
@Override
public Mono<Boolean> resendEmailVerification(
ResendEmailVerificationDTO resendEmailVerificationDTO, String redirectUrl) {
@ -971,21 +850,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
URLEncoder.encode(emailVerificationToken.getEmail(), StandardCharsets.UTF_8),
redirectUrlCopy);
log.debug(
"Email Verification url for email: {}: {}",
emailVerificationToken.getEmail(),
verificationUrl);
Map<String, String> params = new HashMap<>();
params.put("verificationUrl", verificationUrl);
return updateTenantLogoInParams(params, resendEmailVerificationDTO.getBaseUrl())
.flatMap(updatedParams -> emailSender.sendMail(
email,
"Verify your account",
USER_EMAIL_VERIFICATION_EMAIL_TEMPLATE,
updatedParams));
return emailService.sendEmailVerificationEmail(user, verificationUrl);
})
.thenReturn(true);
}
@ -1092,11 +957,7 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
user.setEmailVerified(TRUE);
Mono<Void> redirectionMono = redirectStrategy.sendRedirect(
webFilterExchange.getExchange(), URI.create(postVerificationRedirectUrl));
Mono<User> welcomeEmailMono = sendWelcomeEmail(user, baseUrl);
return repository
.save(user)
.then(Mono.zip(welcomeEmailMono, redirectionMono))
.flatMap(objects -> Mono.just(objects.getT2()));
return repository.save(user).then(redirectionMono);
});
});
}

View File

@ -3,7 +3,6 @@ package com.appsmith.server.services.ce_compatible;
import com.appsmith.external.services.EncryptionService;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.configurations.EmailConfig;
import com.appsmith.server.helpers.RedirectHelper;
import com.appsmith.server.helpers.UserUtils;
import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.ratelimiting.RateLimitService;
@ -12,6 +11,7 @@ import com.appsmith.server.repositories.EmailVerificationTokenRepository;
import com.appsmith.server.repositories.PasswordResetTokenRepository;
import com.appsmith.server.repositories.UserRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.TenantService;
@ -52,7 +52,7 @@ public class UserServiceCECompatibleImpl extends UserServiceCEImpl implements Us
PermissionGroupService permissionGroupService,
UserUtils userUtils,
EmailVerificationTokenRepository emailVerificationTokenRepository,
RedirectHelper redirectHelper,
EmailService emailService,
RateLimitService rateLimitService) {
super(
scheduler,
@ -77,7 +77,7 @@ public class UserServiceCECompatibleImpl extends UserServiceCEImpl implements Us
permissionGroupService,
userUtils,
emailVerificationTokenRepository,
redirectHelper,
emailService,
rateLimitService);
}
}

View File

@ -9,6 +9,7 @@ import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.repositories.UserRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.TenantService;
@ -38,7 +39,8 @@ public class EnvManagerImpl extends EnvManagerCEImpl implements EnvManager {
ConfigService configService,
UserUtils userUtils,
TenantService tenantService,
ObjectMapper objectMapper) {
ObjectMapper objectMapper,
EmailService emailService) {
super(
sessionUserService,
@ -55,6 +57,7 @@ public class EnvManagerImpl extends EnvManagerCEImpl implements EnvManager {
configService,
userUtils,
tenantService,
objectMapper);
objectMapper,
emailService);
}
}

View File

@ -1,8 +1,9 @@
package com.appsmith.server.solutions;
import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.repositories.UserRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserService;
@ -23,8 +24,9 @@ public class UserAndAccessManagementServiceImpl extends UserAndAccessManagementS
UserRepository userRepository,
AnalyticsService analyticsService,
UserService userService,
EmailSender emailSender,
PermissionGroupPermission permissionGroupPermission) {
PermissionGroupPermission permissionGroupPermission,
EmailService emailService,
CommonConfig commonConfig) {
super(
sessionUserService,
@ -33,7 +35,8 @@ public class UserAndAccessManagementServiceImpl extends UserAndAccessManagementS
userRepository,
analyticsService,
userService,
emailSender,
permissionGroupPermission);
permissionGroupPermission,
emailService,
commonConfig);
}
}

View File

@ -7,6 +7,7 @@ import com.appsmith.server.helpers.UserUtils;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.CaptchaService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.UserDataService;
import com.appsmith.server.services.UserService;
import com.appsmith.server.solutions.ce.UserSignupCEImpl;
@ -27,7 +28,8 @@ public class UserSignupImpl extends UserSignupCEImpl implements UserSignup {
EnvManager envManager,
CommonConfig commonConfig,
UserUtils userUtils,
NetworkUtils networkUtils) {
NetworkUtils networkUtils,
EmailService emailService) {
super(
userService,
@ -39,6 +41,7 @@ public class UserSignupImpl extends UserSignupCEImpl implements UserSignup {
envManager,
commonConfig,
userUtils,
networkUtils);
networkUtils,
emailService);
}
}

View File

@ -14,9 +14,9 @@ public interface EnvManagerCE {
List<String> transformEnvContent(String envContent, Map<String, String> changes);
Mono<Void> applyChanges(Map<String, String> changes);
Mono<Void> applyChanges(Map<String, String> changes, String originHeader);
Mono<Void> applyChangesFromMultipartFormData(MultiValueMap<String, Part> formData);
Mono<Void> applyChangesFromMultipartFormData(MultiValueMap<String, Part> formData, String originHeader);
void setAnalyticsEventAction(
Map<String, Object> properties, String newVariable, String originalVariable, String authEnv);

View File

@ -21,6 +21,7 @@ import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.repositories.UserRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.TenantService;
@ -115,6 +116,8 @@ public class EnvManagerCEImpl implements EnvManagerCE {
private final ObjectMapper objectMapper;
private final EmailService emailService;
/**
* This regex pattern matches environment variable declarations like `VAR_NAME=value` or `VAR_NAME="value"` or just
* `VAR_NAME=`. It also defines two named capture groups, `name` and `value`, for the variable's name and value
@ -140,7 +143,8 @@ public class EnvManagerCEImpl implements EnvManagerCE {
ConfigService configService,
UserUtils userUtils,
TenantService tenantService,
ObjectMapper objectMapper) {
ObjectMapper objectMapper,
EmailService emailService) {
this.sessionUserService = sessionUserService;
this.userService = userService;
@ -157,6 +161,7 @@ public class EnvManagerCEImpl implements EnvManagerCE {
this.userUtils = userUtils;
this.tenantService = tenantService;
this.objectMapper = objectMapper;
this.emailService = emailService;
}
/**
@ -335,7 +340,7 @@ public class EnvManagerCEImpl implements EnvManagerCE {
}
@Override
public Mono<Void> applyChanges(Map<String, String> changes) {
public Mono<Void> applyChanges(Map<String, String> changes, String originHeader) {
// This flow is pertinent for any variables that need to change in the .env file or be saved in the tenant
// configuration
return verifyCurrentUserIsSuper()
@ -396,7 +401,7 @@ public class EnvManagerCEImpl implements EnvManagerCE {
commonConfig.setAdminEmails(changesCopy.remove(APPSMITH_ADMIN_EMAILS.name()));
String oldAdminEmailsCsv = originalValues.get(APPSMITH_ADMIN_EMAILS.name());
dependentTasks = dependentTasks
.then(updateAdminUserPolicies(oldAdminEmailsCsv))
.then(updateAdminUserPolicies(oldAdminEmailsCsv, originHeader))
.then();
}
@ -449,7 +454,7 @@ public class EnvManagerCEImpl implements EnvManagerCE {
}
@Override
public Mono<Void> applyChangesFromMultipartFormData(MultiValueMap<String, Part> formData) {
public Mono<Void> applyChangesFromMultipartFormData(MultiValueMap<String, Part> formData, String originHeader) {
return Flux.fromIterable(formData.entrySet())
.flatMap(entry -> {
final String key = entry.getKey();
@ -474,7 +479,7 @@ public class EnvManagerCEImpl implements EnvManagerCE {
});
})
.collectMap(Map.Entry::getKey, Map.Entry::getValue)
.flatMap(this::applyChanges);
.flatMap(changesMap -> this.applyChanges(changesMap, originHeader));
}
@Override
@ -571,7 +576,7 @@ public class EnvManagerCEImpl implements EnvManagerCE {
*
* @param oldAdminEmailsCsv comma separated email addresses that was set as admin email earlier
*/
private Mono<Boolean> updateAdminUserPolicies(String oldAdminEmailsCsv) {
private Mono<Boolean> updateAdminUserPolicies(String oldAdminEmailsCsv, String originHeader) {
Set<String> oldAdminEmails = TextUtils.csvToSet(oldAdminEmailsCsv);
Set<String> newAdminEmails = commonConfig.getAdminEmails();
@ -584,14 +589,48 @@ public class EnvManagerCEImpl implements EnvManagerCE {
Mono<Boolean> removedUsersMono = Flux.fromIterable(removedUsers)
.flatMap(userService::findByEmail)
.collectList()
.flatMap(users -> userUtils.removeSuperUser(users));
.flatMap(userUtils::removeSuperUser);
Mono<Boolean> newUsersMono = Flux.fromIterable(newUsers)
.flatMap(userService::findByEmail)
Flux<User> usersFlux = Flux.fromIterable(newUsers)
.flatMap(email -> userService.findByEmail(email).map(existingUser -> {
if (existingUser == null) {
User newUser = new User();
newUser.setEmail(email);
newUser.setIsEnabled(false);
return newUser;
}
return existingUser;
}))
.cache();
Flux<User> newUsersFlux = usersFlux.filter(user -> !user.isEnabled());
Flux<User> existingUsersFlux = usersFlux.filter(User::isEnabled);
// we are sending email to existing users who are not already super-users
Mono<List<User>> existingUsersWhichAreNotAlreadySuperUsersMono = existingUsersFlux
.filterWhen(user -> userUtils.isSuperUser(user).map(isSuper -> !isSuper))
.collectList();
Mono<Boolean> newUsersMono = newUsersFlux
.flatMap(newUsersFluxUser -> sessionUserService
.getCurrentUser()
.flatMap(invitingUser -> emailService.sendInstanceAdminInviteEmail(
newUsersFluxUser, invitingUser, originHeader, true)))
.collectList()
.flatMap(users -> userUtils.makeSuperUser(users));
.map(results -> results.stream().allMatch(result -> result));
return Mono.when(removedUsersMono, newUsersMono).then(Mono.just(TRUE));
Mono<Boolean> existingUsersMono = existingUsersWhichAreNotAlreadySuperUsersMono.flatMap(users -> userUtils
.makeSuperUser(users)
.flatMap(
success -> Flux.fromIterable(users)
.flatMap(user -> sessionUserService
.getCurrentUser()
.flatMap(invitingUser -> emailService.sendInstanceAdminInviteEmail(
user, invitingUser, originHeader, false)))
.then(Mono.just(success)) // Emit 'success' as the result
));
return Mono.when(removedUsersMono, newUsersMono, existingUsersMono).map(tuple -> TRUE);
}
@Override

View File

@ -1,6 +1,7 @@
package com.appsmith.server.solutions.ce;
import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.PermissionGroup;
import com.appsmith.server.domains.User;
@ -8,9 +9,9 @@ import com.appsmith.server.domains.Workspace;
import com.appsmith.server.dtos.InviteUsersDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.repositories.UserRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserService;
@ -21,6 +22,8 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import java.util.ArrayList;
import java.util.HashMap;
@ -28,7 +31,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.appsmith.server.services.ce.UserServiceCEImpl.INVITE_USER_EMAIL_TEMPLATE;
import static java.lang.Boolean.TRUE;
@Slf4j
@ -40,8 +42,9 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage
private final UserRepository userRepository;
private final AnalyticsService analyticsService;
private final UserService userService;
private final EmailSender emailSender;
private final PermissionGroupPermission permissionGroupPermission;
private final EmailService emailService;
private final CommonConfig commonConfig;
public UserAndAccessManagementServiceCEImpl(
SessionUserService sessionUserService,
@ -50,8 +53,9 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage
UserRepository userRepository,
AnalyticsService analyticsService,
UserService userService,
EmailSender emailSender,
PermissionGroupPermission permissionGroupPermission) {
PermissionGroupPermission permissionGroupPermission,
EmailService emailService,
CommonConfig commonConfig) {
this.sessionUserService = sessionUserService;
this.permissionGroupService = permissionGroupService;
@ -59,8 +63,9 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage
this.userRepository = userRepository;
this.analyticsService = analyticsService;
this.userService = userService;
this.emailSender = emailSender;
this.emailService = emailService;
this.permissionGroupPermission = permissionGroupPermission;
this.commonConfig = commonConfig;
}
/**
@ -125,57 +130,38 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage
// Check if the invited user exists. If yes, return the user, else create a new user by triggering
// createNewUserAndSendInviteEmail. In both the cases, send the appropriate emails
Mono<List<User>> inviteUsersMono = Flux.fromIterable(usernames)
.flatMap(username -> Mono.zip(
Mono.just(username),
workspaceMono,
currentUserMono,
permissionGroupMono,
defaultPermissionGroupsMono))
Mono<List<Tuple2<User, Boolean>>> inviteUsersMonoWithUserCreationMapping = Flux.fromIterable(usernames)
.flatMap(username -> Mono.zip(Mono.just(username), workspaceMono, defaultPermissionGroupsMono))
.flatMap(tuple -> {
String username = tuple.getT1();
Workspace workspace = tuple.getT2();
eventData.put(FieldName.WORKSPACE, workspace);
User currentUser = tuple.getT3();
PermissionGroup permissionGroup = tuple.getT4();
List<PermissionGroup> defaultPermissionGroups = tuple.getT5();
List<PermissionGroup> defaultPermissionGroups = tuple.getT3();
Mono<User> getUserFromDbAndCheckIfUserExists = userRepository
.findByEmail(username)
.flatMap(user -> {
return throwErrorIfUserAlreadyExistsInWorkspace(user, defaultPermissionGroups)
// If no errors, proceed forward
.thenReturn(user);
});
.flatMap(user -> throwErrorIfUserAlreadyExistsInWorkspace(user, defaultPermissionGroups)
.thenReturn(user));
return getUserFromDbAndCheckIfUserExists
.flatMap(existingUser -> {
// The user already existed, just send an email informing that the user has been added
// to a new workspace
log.debug(
"Going to send email to user {} informing that the user has been added to new workspace {}",
existingUser.getEmail(),
workspace.getName());
// Email template parameters initialization below.
Map<String, String> params =
userService.getEmailParams(workspace, currentUser, originHeader, false);
.flatMap(existingUser -> Mono.just(Tuples.of(existingUser, false)))
.switchIfEmpty(Mono.defer(() -> {
User newUser = new User();
newUser.setEmail(username.toLowerCase());
newUser.setIsEnabled(false);
boolean isAdminUser = false;
return userService
.updateTenantLogoInParams(params, originHeader)
.flatMap(updatedParams -> emailSender.sendMail(
existingUser.getEmail(),
"Appsmith: You have been added to a new workspace",
INVITE_USER_EMAIL_TEMPLATE,
updatedParams))
.thenReturn(existingUser);
})
.switchIfEmpty(userService.createNewUserAndSendInviteEmail(
username, originHeader, workspace, currentUser, permissionGroup.getName()));
.userCreate(newUser, isAdminUser)
.flatMap(newCreatedUser -> Mono.just(Tuples.of(newCreatedUser, true)));
}));
})
.collectList()
.cache();
Mono<List<User>> inviteUsersMono = inviteUsersMonoWithUserCreationMapping
.map(tuple2s -> tuple2s.stream().map(Tuple2::getT1).collect(Collectors.toList()))
.cache();
// assign permission group to the invited users.
Mono<PermissionGroup> bulkAddUserResultMono = Mono.zip(permissionGroupMono, inviteUsersMono)
.flatMap(tuple -> {
@ -203,7 +189,28 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage
AnalyticsEvents.EXECUTE_INVITE_USERS, currentUser, analyticsProperties);
});
return bulkAddUserResultMono.then(sendAnalyticsEventMono).then(inviteUsersMono);
Mono<Boolean> sendEmailsMono = Mono.zip(
inviteUsersMonoWithUserCreationMapping, workspaceMono, currentUserMono, permissionGroupMono)
.flatMap(tuple -> {
List<Tuple2<User, Boolean>> users = tuple.getT1();
Workspace workspace = tuple.getT2();
User currentUser = tuple.getT3();
PermissionGroup permissionGroup = tuple.getT4();
return Flux.fromIterable(users)
.flatMap(userTuple -> {
User user = userTuple.getT1();
boolean isNewUser = userTuple.getT2();
return emailService.sendInviteUserToWorkspaceEmail(
currentUser, user, workspace, permissionGroup, originHeader, isNewUser);
})
.all(emailSent -> emailSent);
});
return bulkAddUserResultMono
.then(sendAnalyticsEventMono)
.then(sendEmailsMono)
.then(inviteUsersMono);
}
private Mono<Boolean> throwErrorIfUserAlreadyExistsInWorkspace(

View File

@ -27,7 +27,8 @@ public interface UserSignupCE {
*/
Mono<Void> signupAndLoginFromFormData(ServerWebExchange exchange);
Mono<User> signupAndLoginSuper(UserSignupRequestDTO userFromRequest, ServerWebExchange exchange);
Mono<User> signupAndLoginSuper(
UserSignupRequestDTO userFromRequest, String originHeader, ServerWebExchange exchange);
Mono<Void> signupAndLoginSuperFromFormData(ServerWebExchange exchange);
Mono<Void> signupAndLoginSuperFromFormData(String originHeader, ServerWebExchange exchange);
}

View File

@ -17,6 +17,7 @@ import com.appsmith.server.helpers.UserUtils;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.CaptchaService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.UserDataService;
import com.appsmith.server.services.UserService;
import com.appsmith.server.solutions.EnvManager;
@ -75,6 +76,7 @@ public class UserSignupCEImpl implements UserSignupCE {
private final CommonConfig commonConfig;
private final UserUtils userUtils;
private final NetworkUtils networkUtils;
private final EmailService emailService;
private static final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
@ -90,7 +92,8 @@ public class UserSignupCEImpl implements UserSignupCE {
EnvManager envManager,
CommonConfig commonConfig,
UserUtils userUtils,
NetworkUtils networkUtils) {
NetworkUtils networkUtils,
EmailService emailService) {
this.userService = userService;
this.userDataService = userDataService;
@ -102,6 +105,7 @@ public class UserSignupCEImpl implements UserSignupCE {
this.commonConfig = commonConfig;
this.userUtils = userUtils;
this.networkUtils = networkUtils;
this.emailService = emailService;
}
/**
@ -126,15 +130,15 @@ public class UserSignupCEImpl implements UserSignupCE {
}
// only creating user, welcome email will be sent post user email verification
Mono<UserSignupDTO> createUserAndSendEmailMono = userService
.createUser(user, exchange.getRequest().getHeaders().getOrigin())
Mono<UserSignupDTO> createUserMono = userService
.createUser(user)
.elapsed()
.map(pair -> {
log.debug("UserSignupCEImpl::Time taken for create user and send email: {} ms", pair.getT1());
return pair.getT2();
});
return Mono.zip(createUserAndSendEmailMono, exchange.getSession(), ReactiveSecurityContextHolder.getContext())
return Mono.zip(createUserMono, exchange.getSession(), ReactiveSecurityContextHolder.getContext())
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.INTERNAL_SERVER_ERROR)))
.flatMap(tuple -> {
final User savedUser = tuple.getT1().getUser();
@ -231,7 +235,8 @@ public class UserSignupCEImpl implements UserSignupCE {
});
}
public Mono<User> signupAndLoginSuper(UserSignupRequestDTO userFromRequest, ServerWebExchange exchange) {
public Mono<User> signupAndLoginSuper(
UserSignupRequestDTO userFromRequest, String originHeader, ServerWebExchange exchange) {
Mono<User> userMono = userService
.isUsersEmpty()
.flatMap(isEmpty -> {
@ -280,11 +285,13 @@ public class UserSignupCEImpl implements UserSignupCE {
});
Mono<Void> applyEnvManagerChangesMono = envManager
.applyChanges(Map.of(
APPSMITH_DISABLE_TELEMETRY.name(),
String.valueOf(!userFromRequest.isAllowCollectingAnonymousData()),
APPSMITH_ADMIN_EMAILS.name(),
user.getEmail()))
.applyChanges(
Map.of(
APPSMITH_DISABLE_TELEMETRY.name(),
String.valueOf(!userFromRequest.isAllowCollectingAnonymousData()),
APPSMITH_ADMIN_EMAILS.name(),
user.getEmail()),
originHeader)
// We need a non-empty value for `.elapsed` to work.
.thenReturn(true)
.elapsed()
@ -331,7 +338,7 @@ public class UserSignupCEImpl implements UserSignupCE {
});
}
public Mono<Void> signupAndLoginSuperFromFormData(ServerWebExchange exchange) {
public Mono<Void> signupAndLoginSuperFromFormData(String originHeader, ServerWebExchange exchange) {
return exchange.getFormData()
.map(formData -> {
final UserSignupRequestDTO user = new UserSignupRequestDTO();
@ -358,7 +365,7 @@ public class UserSignupCEImpl implements UserSignupCE {
}
return user;
})
.flatMap(user -> signupAndLoginSuper(user, exchange))
.flatMap(user -> signupAndLoginSuper(user, originHeader, exchange))
.then()
.onErrorResume(error -> {
String referer = exchange.getRequest().getHeaders().getFirst("referer");

View File

@ -1,8 +1,9 @@
package com.appsmith.server.solutions.ce_compatible;
import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.repositories.UserRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserService;
@ -21,8 +22,9 @@ public class UserAndAccessManagementServiceCECompatibleImpl extends UserAndAcces
UserRepository userRepository,
AnalyticsService analyticsService,
UserService userService,
EmailSender emailSender,
PermissionGroupPermission permissionGroupPermission) {
PermissionGroupPermission permissionGroupPermission,
EmailService emailService,
CommonConfig commonConfig) {
super(
sessionUserService,
permissionGroupService,
@ -30,7 +32,8 @@ public class UserAndAccessManagementServiceCECompatibleImpl extends UserAndAcces
userRepository,
analyticsService,
userService,
emailSender,
permissionGroupPermission);
permissionGroupPermission,
emailService,
commonConfig);
}
}

View File

@ -0,0 +1,542 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Reset your password for {{instanceName}}</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 0;
}
</style>
<!--[if mso]>
<noscript
><xml
><o:OfficeDocumentSettings
><o:AllowPNG /><o:PixelsPerInch
>96</o:PixelsPerInch
></o:OfficeDocumentSettings
></xml
></noscript
>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.ogf {
width: 100% !important;
}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link
href="https://fonts.googleapis.com/css?family=Inter:700"
rel="stylesheet"
type="text/css"
/>
<style type="text/css"></style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width: 599px) {
.xc536 {
width: 536px !important;
max-width: 536px;
}
.xc0 {
width: 0px !important;
max-width: 0;
}
.xc16 {
width: 16px !important;
max-width: 16px;
}
}
</style>
<style media="screen and (min-width:599px)">
.moz-text-html .xc536 {
width: 536px !important;
max-width: 536px;
}
.moz-text-html .xc0 {
width: 0px !important;
max-width: 0;
}
.moz-text-html .xc16 {
width: 16px !important;
max-width: 16px;
}
</style>
<style type="text/css">
@media only screen and (max-width: 599px) {
table.fwm {
width: 100% !important;
}
td.fwm {
width: auto !important;
}
}
</style>
<style type="text/css">
u + .emailify a,
#MessageViewBody a,
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
span.MsoHyperlink {
mso-style-priority: 99;
color: inherit;
}
span.MsoHyperlinkFollowed {
mso-style-priority: 99;
color: inherit;
}
td.b .klaviyo-image-block {
display: inline;
vertical-align: middle;
}
@media only screen and (max-width: 599px) {
.emailify {
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
u + .emailify .glist {
margin-left: 1em !important;
}
td.x {
padding-left: 0 !important;
padding-right: 0 !important;
}
.fwm img {
max-width: 100% !important;
height: auto !important;
}
td.b.nw > table,
td.b.nw a {
width: auto !important;
}
td.stk {
border: 0 !important;
}
br.sb {
display: none !important;
}
.thd-1 .i-thumbnail {
display: inline-block !important;
height: auto !important;
overflow: hidden !important;
}
.hd-1 {
display: block !important;
height: auto !important;
overflow: visible !important;
}
.ht-1 {
display: table !important;
height: auto !important;
overflow: visible !important;
}
.hr-1 {
display: table-row !important;
height: auto !important;
overflow: visible !important;
}
.hc-1 {
display: table-cell !important;
height: auto !important;
overflow: visible !important;
}
div.r.pr-16 > table > tbody > tr > td,
div.r.pr-16 > div > table > tbody > tr > td {
padding-right: 16px !important;
}
div.r.pl-16 > table > tbody > tr > td,
div.r.pl-16 > div > table > tbody > tr > td {
padding-left: 16px !important;
}
td.b.fw-1 > table {
width: 100% !important;
}
td.fw-1 > table > tbody > tr > td > a {
display: block !important;
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
td.b.fw-1 > table {
width: 100% !important;
}
td.fw-1 > table > tbody > tr > td {
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
}
</style>
<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
<!--[if gte mso 9]>
<style>
li {
text-indent: -1em;
}
</style>
<![endif]-->
</head>
<body
lang="en"
class="emailify"
style="
mso-line-height-rule: exactly;
word-spacing: normal;
background-color: #d6d6d6;
"
>
<div class="bg" style="background-color: #d6d6d6">
<!--[if mso | IE]>
<table align="left" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]-->
<div
class="r pr-16 pl-16"
style="background-color: #f8f8f8; margin: 0px auto"
>
<table
align="left"
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="background-color: #f8f8f8; width: 100%"
>
<tbody>
<tr>
<td
style="
border: none;
direction: ltr;
font-size: 0;
padding: 32px 32px 32px 32px;
text-align: center;
"
>
<div
class="xc536 ogf c"
style="
font-size: 0;
text-align: left;
direction: ltr;
display: inline-block;
vertical-align: top;
width: 100%;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="border-collapse: separate"
width="600"
>
<tbody>
<tr>
<td
style="
background-color: #ffffff;
border: 1px solid #f1f1f1;
border-radius: 4px;
vertical-align: middle;
padding: 40px 40px;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
width="100%"
>
<tbody>
<tr>
<td
align="left"
class="i m"
style="
font-size: 0;
padding: 0;
padding-bottom: 24px;
word-break: break-word;
border-bottom: 1px solid #cdd5df;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="
border-collapse: collapse;
border-spacing: 0;
"
>
<tbody>
<tr>
<td>
<img
alt=""
height="36"
src="https://assets.appsmith.com/appsmith-logo-no-margin.png"
style="
border: 0;
display: block;
outline: none;
text-decoration: none;
height: 36px;
width: auto;
"
title=""
width="200"
/>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-top: 24px;
padding-bottom: 16px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #4c5664;
line-height: 1.43;
font-weight: 500;
"
>
Hey,
</span>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-bottom: 16px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #4c5664;
line-height: 1.43;
"
>You recently requested to reset your
Appsmith instance password. Let&rsquo;s
get you a new one in two clicks.</span
>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-bottom: 24px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 12px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #6a7585;
line-height: 1.43;
"
>The link expires in two days&rsquo;
time. Better not wait.</span
>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="b fw-1"
style="
font-size: 0;
padding: 0;
padding-bottom: 24px;
word-break: break-word;
border-bottom: 1px solid #cdd5df;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="
border-collapse: separate;
line-height: 100%;
"
>
<tbody>
<tr>
<td
align="center"
bgcolor="#f86a2b"
role="none"
style="
border: none;
border-radius: 4px 4px 4px 4px;
cursor: auto;
mso-padding-alt: 12px 0px 12px 0px;
background: #f86a2b;
"
valign="middle"
>
<a
href="{{resetUrl}}"
style="
display: inline-block;
background: #f86a2b;
color: #ffffff;
font-family: SF Pro Text, Arial,
sans-serif;
font-size: 14px;
font-weight: normal;
line-height: 100%;
margin: 0;
text-decoration: none;
text-transform: none;
padding: 12px 12px 12px 12px;
mso-padding-alt: 0;
border-radius: 4px 4px 4px 4px;
"
target="_blank"
>
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #ffffff;
line-height: 1.43;
"
>Set a new password now</span
></a
>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-top: 24px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 12px;
font-family: SF Pro Text, Arial,
sans-serif;
font-weight: 400;
color: #6a7585;
line-height: 1.43;
"
>1390, Market Street, Suite 200, San
Francisco, California 94102, United
States</span
>
</p>
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,545 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>You're invited to the appsmith instance {{instanceName}}</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 0;
}
</style>
<!--[if mso]>
<noscript
><xml
><o:OfficeDocumentSettings
><o:AllowPNG /><o:PixelsPerInch
>96</o:PixelsPerInch
></o:OfficeDocumentSettings
></xml
></noscript
>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.ogf {
width: 100% !important;
}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link
href="https://fonts.googleapis.com/css?family=Inter:700"
rel="stylesheet"
type="text/css"
/>
<style type="text/css"></style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width: 599px) {
.xc536 {
width: 536px !important;
max-width: 536px;
}
.xc0 {
width: 0px !important;
max-width: 0;
}
.xc16 {
width: 16px !important;
max-width: 16px;
}
}
</style>
<style media="screen and (min-width:599px)">
.moz-text-html .xc536 {
width: 536px !important;
max-width: 536px;
}
.moz-text-html .xc0 {
width: 0px !important;
max-width: 0;
}
.moz-text-html .xc16 {
width: 16px !important;
max-width: 16px;
}
</style>
<style type="text/css">
@media only screen and (max-width: 599px) {
table.fwm {
width: 100% !important;
}
td.fwm {
width: auto !important;
}
}
</style>
<style type="text/css">
u + .emailify a,
#MessageViewBody a,
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
span.MsoHyperlink {
mso-style-priority: 99;
color: inherit;
}
span.MsoHyperlinkFollowed {
mso-style-priority: 99;
color: inherit;
}
td.b .klaviyo-image-block {
display: inline;
vertical-align: middle;
}
@media only screen and (max-width: 599px) {
.emailify {
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
u + .emailify .glist {
margin-left: 1em !important;
}
td.x {
padding-left: 0 !important;
padding-right: 0 !important;
}
.fwm img {
max-width: 100% !important;
height: auto !important;
}
td.b.nw > table,
td.b.nw a {
width: auto !important;
}
td.stk {
border: 0 !important;
}
br.sb {
display: none !important;
}
.thd-1 .i-thumbnail {
display: inline-block !important;
height: auto !important;
overflow: hidden !important;
}
.hd-1 {
display: block !important;
height: auto !important;
overflow: visible !important;
}
.ht-1 {
display: table !important;
height: auto !important;
overflow: visible !important;
}
.hr-1 {
display: table-row !important;
height: auto !important;
overflow: visible !important;
}
.hc-1 {
display: table-cell !important;
height: auto !important;
overflow: visible !important;
}
div.r.pr-16 > table > tbody > tr > td,
div.r.pr-16 > div > table > tbody > tr > td {
padding-right: 16px !important;
}
div.r.pl-16 > table > tbody > tr > td,
div.r.pl-16 > div > table > tbody > tr > td {
padding-left: 16px !important;
}
td.b.fw-1 > table {
width: 100% !important;
}
td.fw-1 > table > tbody > tr > td > a {
display: block !important;
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
td.b.fw-1 > table {
width: 100% !important;
}
td.fw-1 > table > tbody > tr > td {
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
}
</style>
<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
<!--[if gte mso 9]>
<style>
li {
text-indent: -1em;
}
</style>
<![endif]-->
</head>
<body
lang="en"
class="emailify"
style="
mso-line-height-rule: exactly;
word-spacing: normal;
background-color: #d6d6d6;
"
>
<div class="bg" style="background-color: #d6d6d6">
<!--[if mso | IE]>
<table align="left" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]-->
<div
class="r pr-16 pl-16"
style="background-color: #f8f8f8; margin: 0px auto"
>
<table
align="left"
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="background-color: #f8f8f8; width: 100%"
>
<tbody>
<tr>
<td
style="
border: none;
direction: ltr;
font-size: 0;
padding: 32px 32px 32px 32px;
text-align: center;
"
>
<div
class="xc536 ogf c"
style="
font-size: 0;
text-align: left;
direction: ltr;
display: inline-block;
vertical-align: top;
width: 100%;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="border-collapse: separate"
width="600"
>
<tbody>
<tr>
<td
style="
background-color: #ffffff;
border: 1px solid #f1f1f1;
border-radius: 4px;
vertical-align: top;
padding: 40px 40px;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
width="100%"
>
<tbody>
<tr>
<td
align="left"
class="i m"
style="
font-size: 0;
padding: 0;
padding-bottom: 24px;
word-break: break-word;
border-bottom: 1px solid #cdd5df;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="
border-collapse: collapse;
border-spacing: 0;
"
>
<tbody>
<tr>
<td>
<img
alt=""
height="36"
src="https://assets.appsmith.com/appsmith-logo-no-margin.png"
style="
border: 0;
display: block;
outline: none;
text-decoration: none;
height: 36px;
width: auto;
"
title=""
width="200"
/>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-top: 24px;
padding-bottom: 16px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #4c5664;
line-height: 1.43;
font-weight: 500;
"
>
Hey,
</span>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-bottom: 16px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #4c5664;
line-height: 1.43;
"
>{{inviterFirstName}} has invited you to
the Appsmith instance
<b>{{instanceName}}</b></span
>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-bottom: 24px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
font-weight: 400;
color: #4c5664;
line-height: 1.43;
"
>Join your Appsmith instance to get
access to fast, usable, beautiful
apps.</span
>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="b fw-1"
style="
font-size: 0;
padding: 0;
padding-bottom: 24px;
word-break: break-word;
border-bottom: 1px solid #cdd5df;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="
border-collapse: separate;
line-height: 100%;
"
>
<tbody>
<tr>
<td
align="center"
bgcolor="#f86a2b"
role="none"
style="
border: none;
border-radius: 4px 4px 4px 4px;
cursor: auto;
mso-padding-alt: 12px 0px 12px 0px;
background: #f86a2b;
color: #ffffff;
"
valign="middle"
>
<a
href="{{primaryLinkUrl}}"
style="
display: inline-block;
font-family: SF Pro Text, Arial,
sans-serif;
font-size: 14px;
background: #f86a2b;
color: #ffffff;
font-weight: normal;
line-height: 100%;
margin: 0;
text-decoration: none;
text-transform: none;
padding: 12px 12px 12px 12px;
mso-padding-alt: 0;
border-radius: 4px 4px 4px 4px;
"
target="_blank"
>
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
line-height: 1.43;
color: #ffffff;
"
>{{primaryLinkText}}</span
></a
>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-top: 24px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 12px;
font-family: SF Pro Text, Arial,
sans-serif;
font-weight: 400;
color: #6a7585;
line-height: 1.43;
"
>1390, Market Street, Suite 200, San
Francisco, California 94102, United
States</span
>
</p>
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,547 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>You're invited to the workspace {{inviterWorkspaceName}}</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 0;
}
</style>
<!--[if mso]>
<noscript
><xml
><o:OfficeDocumentSettings
><o:AllowPNG /><o:PixelsPerInch
>96</o:PixelsPerInch
></o:OfficeDocumentSettings
></xml
></noscript
>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.ogf {
width: 100% !important;
}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link
href="https://fonts.googleapis.com/css?family=Inter:700"
rel="stylesheet"
type="text/css"
/>
<style type="text/css"></style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width: 599px) {
.xc536 {
width: 536px !important;
max-width: 536px;
}
.xc0 {
width: 0px !important;
max-width: 0;
}
.xc16 {
width: 16px !important;
max-width: 16px;
}
}
</style>
<style media="screen and (min-width:599px)">
.moz-text-html .xc536 {
width: 536px !important;
max-width: 536px;
}
.moz-text-html .xc0 {
width: 0px !important;
max-width: 0;
}
.moz-text-html .xc16 {
width: 16px !important;
max-width: 16px;
}
</style>
<style type="text/css">
@media only screen and (max-width: 599px) {
table.fwm {
width: 100% !important;
}
td.fwm {
width: auto !important;
}
}
</style>
<style type="text/css">
u + .emailify a,
#MessageViewBody a,
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
span.MsoHyperlink {
mso-style-priority: 99;
color: inherit;
}
span.MsoHyperlinkFollowed {
mso-style-priority: 99;
color: inherit;
}
td.b .klaviyo-image-block {
display: inline;
vertical-align: middle;
}
@media only screen and (max-width: 599px) {
.emailify {
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
u + .emailify .glist {
margin-left: 1em !important;
}
td.x {
padding-left: 0 !important;
padding-right: 0 !important;
}
.fwm img {
max-width: 100% !important;
height: auto !important;
}
td.b.nw > table,
td.b.nw a {
width: auto !important;
}
td.stk {
border: 0 !important;
}
br.sb {
display: none !important;
}
.thd-1 .i-thumbnail {
display: inline-block !important;
height: auto !important;
overflow: hidden !important;
}
.hd-1 {
display: block !important;
height: auto !important;
overflow: visible !important;
}
.ht-1 {
display: table !important;
height: auto !important;
overflow: visible !important;
}
.hr-1 {
display: table-row !important;
height: auto !important;
overflow: visible !important;
}
.hc-1 {
display: table-cell !important;
height: auto !important;
overflow: visible !important;
}
div.r.pr-16 > table > tbody > tr > td,
div.r.pr-16 > div > table > tbody > tr > td {
padding-right: 16px !important;
}
div.r.pl-16 > table > tbody > tr > td,
div.r.pl-16 > div > table > tbody > tr > td {
padding-left: 16px !important;
}
td.b.fw-1 > table {
width: 100% !important;
}
td.fw-1 > table > tbody > tr > td > a {
display: block !important;
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
td.b.fw-1 > table {
width: 100% !important;
}
td.fw-1 > table > tbody > tr > td {
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
}
</style>
<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
<!--[if gte mso 9]>
<style>
li {
text-indent: -1em;
}
</style>
<![endif]-->
</head>
<body
lang="en"
class="emailify"
style="
mso-line-height-rule: exactly;
word-spacing: normal;
background-color: #d6d6d6;
"
>
<div class="bg" style="background-color: #d6d6d6">
<!--[if mso | IE]>
<table align="left" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]-->
<div
class="r pr-16 pl-16"
style="background-color: #f8f8f8; margin: 0px auto"
>
<table
align="left"
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="background-color: #f8f8f8; width: 100%"
>
<tbody>
<tr>
<td
style="
border: none;
direction: ltr;
font-size: 0;
padding: 32px 32px 32px 32px;
text-align: center;
"
>
<div
class="xc536 ogf c"
style="
font-size: 0;
text-align: left;
direction: ltr;
display: inline-block;
vertical-align: top;
width: 100%;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="border-collapse: separate"
width="600"
>
<tbody>
<tr>
<td
style="
background-color: #ffffff;
border: 1px solid #f1f1f1;
border-radius: 4px;
vertical-align: top;
padding: 40px 40px;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
width="100%"
>
<tbody>
<tr>
<td
align="left"
class="i m"
style="
font-size: 0;
padding: 0;
padding-bottom: 24px;
word-break: break-word;
border-bottom: 1px solid #cdd5df;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="
border-collapse: collapse;
border-spacing: 0;
"
>
<tbody>
<tr>
<td>
<img
alt=""
height="36"
src="https://assets.appsmith.com/appsmith-logo-no-margin.png"
style="
border: 0;
display: block;
outline: none;
text-decoration: none;
height: 36px;
width: auto;
"
title=""
width="200"
/>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-top: 24px;
padding-bottom: 16px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #4c5664;
line-height: 1.43;
font-weight: 500;
"
>
Hey,
</span>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-bottom: 16px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #4c5664;
line-height: 1.43;
"
>{{inviterFirstName}} has invited you as
<b>{{role}}</b> to the Appsmith
workspace
<b>{{inviterWorkspaceName}}</b></span
>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-bottom: 24px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
font-weight: 400;
color: #4c5664;
line-height: 1.43;
"
>Join your Appsmith workspace to get
access to fast, usable, beautiful
apps.</span
>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="b fw-1"
style="
font-size: 0;
padding: 0;
padding-bottom: 24px;
word-break: break-word;
border-bottom: 1px solid #cdd5df;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="
border-collapse: separate;
line-height: 100%;
"
>
<tbody>
<tr>
<td
align="center"
bgcolor="#f86a2b"
role="none"
style="
border: none;
border-radius: 4px 4px 4px 4px;
cursor: auto;
mso-padding-alt: 12px 0px 12px 0px;
background: #f86a2b;
color: #ffffff;
"
valign="middle"
>
<a
href="{{primaryLinkUrl}}"
style="
display: inline-block;
font-family: SF Pro Text, Arial,
sans-serif;
font-size: 14px;
background: #f86a2b;
color: #ffffff;
font-weight: normal;
line-height: 100%;
margin: 0;
text-decoration: none;
text-transform: none;
padding: 12px 12px 12px 12px;
mso-padding-alt: 0;
border-radius: 4px 4px 4px 4px;
"
target="_blank"
>
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
line-height: 1.43;
color: #ffffff;
"
>Go to your Appsmith
workspace</span
></a
>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-top: 24px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 12px;
font-family: SF Pro Text, Arial,
sans-serif;
font-weight: 400;
color: #6a7585;
line-height: 1.43;
"
>1390, Market Street, Suite 200, San
Francisco, California 94102, United
States</span
>
</p>
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,547 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>You're invited to the workspace {{inviterWorkspaceName}}</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 0;
}
</style>
<!--[if mso]>
<noscript
><xml
><o:OfficeDocumentSettings
><o:AllowPNG /><o:PixelsPerInch
>96</o:PixelsPerInch
></o:OfficeDocumentSettings
></xml
></noscript
>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.ogf {
width: 100% !important;
}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link
href="https://fonts.googleapis.com/css?family=Inter:700"
rel="stylesheet"
type="text/css"
/>
<style type="text/css"></style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width: 599px) {
.xc536 {
width: 536px !important;
max-width: 536px;
}
.xc0 {
width: 0px !important;
max-width: 0;
}
.xc16 {
width: 16px !important;
max-width: 16px;
}
}
</style>
<style media="screen and (min-width:599px)">
.moz-text-html .xc536 {
width: 536px !important;
max-width: 536px;
}
.moz-text-html .xc0 {
width: 0px !important;
max-width: 0;
}
.moz-text-html .xc16 {
width: 16px !important;
max-width: 16px;
}
</style>
<style type="text/css">
@media only screen and (max-width: 599px) {
table.fwm {
width: 100% !important;
}
td.fwm {
width: auto !important;
}
}
</style>
<style type="text/css">
u + .emailify a,
#MessageViewBody a,
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
span.MsoHyperlink {
mso-style-priority: 99;
color: inherit;
}
span.MsoHyperlinkFollowed {
mso-style-priority: 99;
color: inherit;
}
td.b .klaviyo-image-block {
display: inline;
vertical-align: middle;
}
@media only screen and (max-width: 599px) {
.emailify {
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
u + .emailify .glist {
margin-left: 1em !important;
}
td.x {
padding-left: 0 !important;
padding-right: 0 !important;
}
.fwm img {
max-width: 100% !important;
height: auto !important;
}
td.b.nw > table,
td.b.nw a {
width: auto !important;
}
td.stk {
border: 0 !important;
}
br.sb {
display: none !important;
}
.thd-1 .i-thumbnail {
display: inline-block !important;
height: auto !important;
overflow: hidden !important;
}
.hd-1 {
display: block !important;
height: auto !important;
overflow: visible !important;
}
.ht-1 {
display: table !important;
height: auto !important;
overflow: visible !important;
}
.hr-1 {
display: table-row !important;
height: auto !important;
overflow: visible !important;
}
.hc-1 {
display: table-cell !important;
height: auto !important;
overflow: visible !important;
}
div.r.pr-16 > table > tbody > tr > td,
div.r.pr-16 > div > table > tbody > tr > td {
padding-right: 16px !important;
}
div.r.pl-16 > table > tbody > tr > td,
div.r.pl-16 > div > table > tbody > tr > td {
padding-left: 16px !important;
}
td.b.fw-1 > table {
width: 100% !important;
}
td.fw-1 > table > tbody > tr > td > a {
display: block !important;
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
td.b.fw-1 > table {
width: 100% !important;
}
td.fw-1 > table > tbody > tr > td {
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
}
</style>
<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
<!--[if gte mso 9]>
<style>
li {
text-indent: -1em;
}
</style>
<![endif]-->
</head>
<body
lang="en"
class="emailify"
style="
mso-line-height-rule: exactly;
word-spacing: normal;
background-color: #d6d6d6;
"
>
<div class="bg" style="background-color: #d6d6d6">
<!--[if mso | IE]>
<table align="left" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]-->
<div
class="r pr-16 pl-16"
style="background-color: #f8f8f8; margin: 0px auto"
>
<table
align="left"
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="background-color: #f8f8f8; width: 100%"
>
<tbody>
<tr>
<td
style="
border: none;
direction: ltr;
font-size: 0;
padding: 32px 32px 32px 32px;
text-align: center;
"
>
<div
class="xc536 ogf c"
style="
font-size: 0;
text-align: left;
direction: ltr;
display: inline-block;
vertical-align: top;
width: 100%;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="border-collapse: separate"
width="600"
>
<tbody>
<tr>
<td
style="
background-color: #ffffff;
border: 1px solid #f1f1f1;
border-radius: 4px;
vertical-align: top;
padding: 40px 40px;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
width="100%"
>
<tbody>
<tr>
<td
align="left"
class="i m"
style="
font-size: 0;
padding: 0;
padding-bottom: 24px;
word-break: break-word;
border-bottom: 1px solid #cdd5df;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="
border-collapse: collapse;
border-spacing: 0;
"
>
<tbody>
<tr>
<td>
<img
alt=""
height="36"
src="https://assets.appsmith.com/appsmith-logo-no-margin.png"
style="
border: 0;
display: block;
outline: none;
text-decoration: none;
height: 36px;
width: auto;
"
title=""
width="200"
/>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-top: 24px;
padding-bottom: 16px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #4c5664;
line-height: 1.43;
font-weight: 500;
"
>
Hey,
</span>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-bottom: 16px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
color: #4c5664;
line-height: 1.43;
"
>{{inviterFirstName}} has invited you as
<b>{{role}}</b> access to the Appsmith
workspace
<b>{{inviterWorkspaceName}}</b></span
>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-bottom: 24px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
font-weight: 400;
color: #4c5664;
line-height: 1.43;
"
>Join your Appsmith workspace to get
access to fast, usable, beautiful
apps.</span
>
</p>
</div>
</td>
</tr>
<tr>
<td
align="left"
class="b fw-1"
style="
font-size: 0;
padding: 0;
padding-bottom: 24px;
word-break: break-word;
border-bottom: 1px solid #cdd5df;
"
>
<table
border="0"
cellpadding="0"
cellspacing="0"
role="none"
style="
border-collapse: separate;
line-height: 100%;
"
>
<tbody>
<tr>
<td
align="center"
bgcolor="#f86a2b"
role="none"
style="
border: none;
border-radius: 4px 4px 4px 4px;
cursor: auto;
mso-padding-alt: 12px 0px 12px 0px;
background: #f86a2b;
color: #ffffff;
"
valign="middle"
>
<a
href="{{primaryLinkUrl}}"
style="
display: inline-block;
font-family: SF Pro Text, Arial,
sans-serif;
font-size: 14px;
background: #f86a2b;
color: #ffffff;
font-weight: normal;
line-height: 100%;
margin: 0;
text-decoration: none;
text-transform: none;
padding: 12px 12px 12px 12px;
mso-padding-alt: 0;
border-radius: 4px 4px 4px 4px;
"
target="_blank"
>
<span
style="
font-size: 14px;
font-family: SF Pro Text, Arial,
sans-serif;
line-height: 1.43;
color: #ffffff;
"
>Join your Appsmith
workspace</span
></a
>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td
align="left"
class="x m"
style="
font-size: 0;
padding-top: 24px;
word-break: break-word;
"
>
<div style="text-align: left">
<p style="margin: 0; text-align: left">
<span
style="
font-size: 12px;
font-family: SF Pro Text, Arial,
sans-serif;
font-weight: 400;
color: #6a7585;
line-height: 1.43;
"
>1390, Market Street, Suite 200, San
Francisco, California 94102, United
States</span
>
</p>
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -1,286 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<!--<![endif]-->
<!--[if (gte mso 9)|(IE)]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if (gte mso 9)|(IE)]>
<style type="text/css">
body {
width: 600px;
margin: 0 auto;
}
table {
border-collapse: collapse;
}
table, td {
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
-ms-interpolation-mode: bicubic;
}
</style>
<![endif]-->
<style type="text/css">
body, p, div {
font-family: arial, helvetica, sans-serif;
font-size: 14px;
}
body {
color: #000000;
}
body a {
color: #1188E6;
text-decoration: none;
}
p {
margin: 0;
padding: 0;
}
table.wrapper {
width: 100% !important;
table-layout: fixed;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
img.max-width {
max-width: 100% !important;
}
.column.of-2 {
width: 50%;
}
.column.of-3 {
width: 33.333%;
}
.column.of-4 {
width: 25%;
}
@media screen and (max-width: 480px) {
.preheader .rightColumnContent,
.footer .rightColumnContent {
text-align: left !important;
}
.preheader .rightColumnContent div,
.preheader .rightColumnContent span,
.footer .rightColumnContent div,
.footer .rightColumnContent span {
text-align: left !important;
}
.preheader .rightColumnContent,
.preheader .leftColumnContent {
font-size: 80% !important;
padding: 5px 0;
}
table.wrapper-mobile {
width: 100% !important;
table-layout: fixed;
}
img.max-width {
height: auto !important;
max-width: 100% !important;
}
a.bulletproof-button {
display: block !important;
width: auto !important;
font-size: 80%;
padding-left: 0 !important;
padding-right: 0 !important;
}
.columns {
width: 100% !important;
}
.column {
display: block !important;
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
}
</style>
<!--user entered Head Start--><!--End Head user entered-->
</head>
<body>
<center class="wrapper" data-link-color="#1188E6"
data-body-style="font-size:14px; font-family:arial,helvetica,sans-serif; color:#000000; background-color:#FFFFFF;">
<div class="webkit">
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#FFFFFF">
<tbody>
<tr>
<td valign="top" bgcolor="#FFFFFF" width="100%">
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0"
cellspacing="0" border="0">
<tbody>
<tr>
<td width="100%">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td>
<!--[if mso]>
<center>
<table>
<tr>
<td width="600">
<![endif]-->
<table width="100%" cellpadding="0" cellspacing="0" border="0"
style="width:100%; max-width:600px;" align="center">
<tbody>
<tr>
<td role="modules-container"
style="padding:0px 0px 0px 0px; color:#000000; text-align:left;"
bgcolor="#ffffff" width="100%" align="left">
<table class="module preheader preheader-hide" role="module"
data-type="preheader" border="0" cellpadding="0"
cellspacing="0" width="100%"
style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
<tbody>
<tr>
<td role="module-content">
<p></p>
</td>
</tr>
</tbody>
</table>
<table class="wrapper" role="module" data-type="image"
border="0" cellpadding="0" cellspacing="0" width="100%"
style="table-layout: fixed;">
<tbody>
<tr>
<td style="font-size:6px; line-height:10px; padding-top:36px;"
valign="top" align="center">
<div style="display:block;padding:02px;background-color:#FEB811">
</div>
<div style="display:block;padding:10px;margin-bottom: 40px; color: #393939;background-color:#FFF8E2">
<svg width="50" height="50" style="fill:#FEB811"clip-rule="evenodd" fill-rule="evenodd" color="yellow" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m2.095 19.886 9.248-16.5c.133-.237.384-.384.657-.384.272 0 .524.147.656.384l9.248 16.5c.064.115.096.241.096.367 0 .385-.309.749-.752.749h-18.496c-.44 0-.752-.36-.752-.749 0-.126.031-.252.095-.367zm9.907-6.881c-.414 0-.75.336-.75.75v3.5c0 .414.336.75.75.75s.75-.336.75-.75v-3.5c0-.414-.336-.75-.75-.75zm-.002-3c-.552 0-1 .448-1 1s.448 1 1 1 1-.448 1-1-.448-1-1-1z" fill-rule="nonzero"/></svg>
<div style="margin:8px 16px 16px 16px;font-size:18px;line-height: 35px;">
<i class="fa-solid fa-triangle-exclamation"></i>
<b>Commenting is deprecated</b><br>
We will be removing comments from Appsmith in v1.7.12<br>
<a href="https://appsmith.notion.site/Deprecating-real-time-commenting-60a307d2c5e1485b85ff95afebb616eb" target ="_blank" style="text-decoration: underline;font-size:16px;color:#393939;">Learn More</a>
</div>
</div>
</td>
</tr>
<tr>
<td style="font-size:6px; line-height:10px; padding-top:px;"
valign="top" align="center">
<a href="https://www.appsmith.com/">
<img width="50px" src="https://assets.appsmith.com/email/appsmith_logo_20x20.png" />
</a>
</td>
</tr>
</tbody>
</table>
<table class="module" role="module" data-type="text" border="0"
cellpadding="0" cellspacing="0" width="100%"
style="table-layout: fixed;">
<tbody>
<tr>
<td style="padding:0px 0px 18px 0px; line-height:22px; text-align:center; background-color:#ffffff;"
height="100%" valign="top" bgcolor="#ffffff"
role="module-content">
<div style="margin-top:37px;font-size:18px;line-height: 35px;">
<b>{{Commenter_Name}}</b>,
{{#NewComment}} added {{/NewComment}}
{{#Resolved}} resolved {{/Resolved}}
{{#Replied}} replied to {{/Replied}}
{{#Mentioned}} mentioned you in {{/Mentioned}}
{{#Resolved}} a comment thread {{/Resolved}}
{{^Resolved}} a comment on {{/Resolved}}
<br><b>{{Application_Name}}, {{Page_Name}}</b>:
</div>
{{#Comment_Body}}
<div style="display:block;padding:10px;margin-top: 40px; color: #5c5959;background-color:#F0F0F0">
{{ . }}
</div>
{{/Comment_Body}}
<div style="margin-top:40px">
<a href="{{commentUrl}}"
style="background-color:#ff6d2d; border:1px solid #ff6d2d; border-color:#ff6d2d; border-width:1px; color:#ffffff; display:inline-block; font-weight:400; letter-spacing:0px; line-height:6px; padding:12px 18px 12px 18px; text-align:center; text-decoration:none; border-style:solid; font-family:tahoma,geneva,sans-serif; font-size:16px;"
target="_blank">VIEW THE COMMENT</a>
</div>
<!-- <div style="margin-top:23px">You can answer by replying to this email.</div>-->
<hr style="margin-top:40px;margin-bottom:40px;height:0px;border:0; border-top: 1px solid #E0DEDE;" />
<div><a href="{{UnsubscribeLink}}" style="text-decoration:underline">Turn off comment notifications for this thread</a></div>
<div style="font-size:12px;line-height:19px;color:#A9A7A7;margin-top:17px">
Appsmith is an open source framework to build admin panels, CRUD apps and workflows. Build everything you need, 10x faster.
</div>
<div style="margin-top:32px">
<a href="https://www.youtube.com/appsmith" style="text-decoration:none">
<img width="20px" src="https://assets.appsmith.com/email/ic_youtube.png" /></a>
<a href="https://twitter.com/theappsmith" style="margin-left:25px;margin-right:25px;text-decoration:none">
<img width="20px" src="https://assets.appsmith.com/email/ic_twitter.png" /></a>
<a href="https://www.linkedin.com/company/appsmith" style="text-decoration:none">
<img width="20px" src="https://assets.appsmith.com/email/ic_linkedin.png" /></a>
</div>
<div style="font-size:12px;line-height:19px;color:#A9A7A7;margin-top:42px">
2261 Market Street #4147, United States, San Francisco, California, 94114, US
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if mso]>
</td>
</tr>
</table>
</center>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</center>
</body>
</html>

View File

@ -1,123 +0,0 @@
<!doctype html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> Reset password for your Appsmith account </title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a{padding:0;}body{margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}table,td{border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;}img{border:0;height:auto;line-height:100%;outline:none;text-decoration:none;-ms-interpolation-mode:bicubic;}p{display:block;margin:0;}
</style>
<!--[if mso]> <noscript><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.ogf{width:100% !important;}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Inter:700" rel="stylesheet" type="text/css">
<style type="text/css">
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:599px){.xc536{width:536px!important;max-width:536px;}.xc0{width:0px!important;max-width:0;}.xc16{width:16px!important;max-width:16px;}}
</style>
<style media="screen and (min-width:599px)">.moz-text-html .xc536{width:536px!important;max-width:536px;}.moz-text-html .xc0{width:0px!important;max-width:0;}.moz-text-html .xc16{width:16px!important;max-width:16px;}
</style>
<style type="text/css">
@media only screen and (max-width:599px){table.fwm{width:100%!important;}td.fwm{width:auto!important;}}
</style>
<style type="text/css">
u+.emailify a,#MessageViewBody a,a[x-apple-data-detectors]{color:inherit!important;text-decoration:none!important;font-size:inherit!important;font-family:inherit!important;font-weight:inherit!important;line-height:inherit!important;}span.MsoHyperlink{mso-style-priority:99;color:inherit;}span.MsoHyperlinkFollowed{mso-style-priority:99;color:inherit;}td.b .klaviyo-image-block{display:inline;vertical-align:middle;}
@media only screen and (max-width:599px){.emailify{height:100%!important;margin:0!important;padding:0!important;width:100%!important;}u+.emailify .glist{margin-left:1em!important;}td.x{padding-left:0!important;padding-right:0!important;}.fwm img{max-width:100%!important;height:auto!important;}td.b.nw>table,td.b.nw a{width:auto!important;}td.stk{border:0!important;}br.sb{display:none!important;}.thd-1 .i-thumbnail{display:inline-block!important;height:auto!important;overflow:hidden!important;}.hd-1{display:block!important;height:auto!important;overflow:visible!important;}.ht-1{display:table!important;height:auto!important;overflow:visible!important;}.hr-1{display:table-row!important;height:auto!important;overflow:visible!important;}.hc-1{display:table-cell!important;height:auto!important;overflow:visible!important;}div.r.pr-16>table>tbody>tr>td,div.r.pr-16>div>table>tbody>tr>td{padding-right:16px!important}div.r.pl-16>table>tbody>tr>td,div.r.pl-16>div>table>tbody>tr>td{padding-left:16px!important}td.b.fw-1>table{width:100%!important}td.fw-1>table>tbody>tr>td>a{display:block!important;width:100%!important;padding-left:0!important;padding-right:0!important;}td.b.fw-1>table{width:100%!important}td.fw-1>table>tbody>tr>td{width:100%!important;padding-left:0!important;padding-right:0!important;}}
</style>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<!--[if gte mso 9]>
<style>li{text-indent:-1em;}
</style>
<![endif]-->
</head>
<body lang="en" class="emailify" style="mso-line-height-rule:exactly;word-spacing:normal;background-color:#d6d6d6;"><div class="bg" style="background-color:#d6d6d6;">
<!--[if mso | IE]>
<table align="center" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]--><div class="r pr-16 pl-16" style="background:#f8f8f8;background-color:#f8f8f8;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="none" style="background:#f8f8f8;background-color:#f8f8f8;width:100%;"><tbody><tr><td style="border:none;direction:ltr;font-size:0;padding:32px 32px 8px 32px;text-align:left;">
<!--[if mso | IE]>
<table role="none" border="0" cellpadding="0" cellspacing="0"><tr><td class="m-outlook c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf m c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border:none;vertical-align:top;" width="100%"><tbody><tr><td align="center" class="d" style="font-size:0;padding:0;word-break:break-word;"><h1 style="border-top:solid 8px #f86a2b;font-size:1px;margin:0px auto;width:100%;"></h1>
<!--[if mso | IE]>
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 8px #f86a2b;font-size:1px;margin:0px auto;width:536px;" role="none" width="536px"><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="g-outlook" style="vertical-align:top;width:0;">
<![endif]--><div class="xc0 ogf g" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="background-color:#fffffe;border:none;vertical-align:top;padding:40px 24px 24px 24px;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td align="center" class="i m" style="font-size:0;padding:0;padding-bottom:16px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border-collapse:collapse;border-spacing:0;"><tbody><tr><td style="width:200px;"> <img alt="" height="auto" src="https://assets.appsmith.com/appsmith-logo-no-margin.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" title="" width="200">
</td></tr></tbody></table>
</td></tr><tr><td class="s m" style="font-size:0;padding:0;padding-bottom:16px;word-break:break-word;"><div style="height:1px;line-height:1px;">&#8202;</div>
</td></tr><tr><td align="center" class="x m" style="font-size:0;padding-bottom:16px;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:20px;font-family:SF Pro Text,Arial,sans-serif;font-weight:600;color:#000000;line-height:24px;">Password reset link for Appsmith apps</span></p></div>
</td></tr><tr><td align="center" class="x m" style="font-size:0;padding-bottom:16px;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:16px;font-family:SF Pro Text,Arial,sans-serif;font-weight:400;color:#000000;line-height:24px;">Forgotten your password? No worries, we&rsquo;ve got you covered. This link will expire in 48 hours.</span></p></div>
</td></tr><tr><td class="s m" style="font-size:0;padding:0;padding-bottom:16px;word-break:break-word;"><div style="height:4px;line-height:4px;">&#8202;</div>
</td></tr><tr><td align="center" class="b fw-1" style="font-size:0;padding:0;padding-bottom:0;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border-collapse:separate;width:154px;line-height:100%;"><tbody><tr><td align="center" bgcolor="#f86a2b" role="none" style="border:none;border-radius:4px 4px 4px 4px;cursor:auto;mso-padding-alt:12px 0px 12px 0px;background:#f86a2b;" valign="middle"> <a href="{{resetUrl}}" style="display:inline-block;width:154px;background:#f86a2b;color:#ffffff;font-family:Inter,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:100%;margin:0;text-decoration:none;text-transform:none;padding:12px 0px 12px 0px;mso-padding-alt:0;border-radius:4px 4px 4px 4px;" target="_blank"> <span style="font-size:14px;font-family:Inter,Arial,sans-serif;font-weight:700;color:#ffffff;line-height:17px;">Reset Password</span></a>
</td></tr></tbody></table>
</td></tr></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<table align="center" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]--><div class="r pr-16 pl-16" style="background:#f8f8f8;background-color:#f8f8f8;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="none" style="background:#f8f8f8;background-color:#f8f8f8;width:100%;"><tbody><tr><td style="border:none;direction:ltr;font-size:0;padding:16px 32px 16px 32px;text-align:left;">
<!--[if mso | IE]>
<table role="none" border="0" cellpadding="0" cellspacing="0"><tr><td class="m-outlook c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf m c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border:none;vertical-align:top;" width="100%"><tbody><tr><td align="center" class="d" style="font-size:0;padding:0;word-break:break-word;"><p style="border-top:solid 1px #cccccc;font-size:1px;margin:0px auto;width:100%;"></p>
<!--[if mso | IE]>
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #cccccc;font-size:1px;margin:0px auto;width:536px;" role="none" width="536px"><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="g-outlook" style="vertical-align:top;width:16px;">
<![endif]--><div class="xc16 ogf g" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="border:none;vertical-align:top;padding:0px 12px 0px 12px;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td align="left" class="i fw-1 m" style="font-size:0;padding:0;padding-bottom:8px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border-collapse:collapse;border-spacing:0;" class="fwm"><tbody><tr><td style="width:512px;" class="fwm"> <img alt="" height="auto" src="https://emailify.b-cdn.net/f76e7fcbed28d8e5b3acc27c5109a06e.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" title="" width="512">
</td></tr></tbody></table>
</td></tr><tr><td align="center" class="x" style="font-size:0;padding-bottom:0;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:14px;font-family:SF Pro Text,Arial,sans-serif;font-weight:400;color:#b3b3b3;line-height:22px;">Appsmith is an open source framework that helps developer and companies to build powerful internal tools.</span></p></div>
</td></tr></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]--></div>
</body>
</html>

View File

@ -1,123 +0,0 @@
<!doctype html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{inviterFirstName}} has invited you to collaborate</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a{padding:0;}body{margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}table,td{border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;}img{border:0;height:auto;line-height:100%;outline:none;text-decoration:none;-ms-interpolation-mode:bicubic;}p{display:block;margin:0;}
</style>
<!--[if mso]> <noscript><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.ogf{width:100% !important;}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Inter:700" rel="stylesheet" type="text/css">
<style type="text/css">
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:599px){.xc536{width:536px!important;max-width:536px;}.xc0{width:0px!important;max-width:0;}.xc16{width:16px!important;max-width:16px;}}
</style>
<style media="screen and (min-width:599px)">.moz-text-html .xc536{width:536px!important;max-width:536px;}.moz-text-html .xc0{width:0px!important;max-width:0;}.moz-text-html .xc16{width:16px!important;max-width:16px;}
</style>
<style type="text/css">
@media only screen and (max-width:599px){table.fwm{width:100%!important;}td.fwm{width:auto!important;}}
</style>
<style type="text/css">
u+.emailify a,#MessageViewBody a,a[x-apple-data-detectors]{color:inherit!important;text-decoration:none!important;font-size:inherit!important;font-family:inherit!important;font-weight:inherit!important;line-height:inherit!important;}span.MsoHyperlink{mso-style-priority:99;color:inherit;}span.MsoHyperlinkFollowed{mso-style-priority:99;color:inherit;}td.b .klaviyo-image-block{display:inline;vertical-align:middle;}
@media only screen and (max-width:599px){.emailify{height:100%!important;margin:0!important;padding:0!important;width:100%!important;}u+.emailify .glist{margin-left:1em!important;}td.x{padding-left:0!important;padding-right:0!important;}.fwm img{max-width:100%!important;height:auto!important;}td.b.nw>table,td.b.nw a{width:auto!important;}td.stk{border:0!important;}br.sb{display:none!important;}.thd-1 .i-thumbnail{display:inline-block!important;height:auto!important;overflow:hidden!important;}.hd-1{display:block!important;height:auto!important;overflow:visible!important;}.ht-1{display:table!important;height:auto!important;overflow:visible!important;}.hr-1{display:table-row!important;height:auto!important;overflow:visible!important;}.hc-1{display:table-cell!important;height:auto!important;overflow:visible!important;}div.r.pr-16>table>tbody>tr>td,div.r.pr-16>div>table>tbody>tr>td{padding-right:16px!important}div.r.pl-16>table>tbody>tr>td,div.r.pl-16>div>table>tbody>tr>td{padding-left:16px!important}td.b.fw-1>table{width:100%!important}td.fw-1>table>tbody>tr>td>a{display:block!important;width:100%!important;padding-left:0!important;padding-right:0!important;}td.b.fw-1>table{width:100%!important}td.fw-1>table>tbody>tr>td{width:100%!important;padding-left:0!important;padding-right:0!important;}}
</style>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<!--[if gte mso 9]>
<style>li{text-indent:-1em;}
</style>
<![endif]-->
</head>
<body lang="en" class="emailify" style="mso-line-height-rule:exactly;word-spacing:normal;background-color:#d6d6d6;"><div class="bg" style="background-color:#d6d6d6;">
<!--[if mso | IE]>
<table align="center" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]--><div class="r pr-16 pl-16" style="background:#f8f8f8;background-color:#f8f8f8;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="none" style="background:#f8f8f8;background-color:#f8f8f8;width:100%;"><tbody><tr><td style="border:none;direction:ltr;font-size:0;padding:32px 32px 8px 32px;text-align:left;">
<!--[if mso | IE]>
<table role="none" border="0" cellpadding="0" cellspacing="0"><tr><td class="m-outlook c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf m c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border:none;vertical-align:top;" width="100%"><tbody><tr><td align="center" class="d" style="font-size:0;padding:0;word-break:break-word;"><h1 style="border-top:solid 8px #f86a2b;font-size:1px;margin:0px auto;width:100%;"></h1>
<!--[if mso | IE]>
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 8px #f86a2b;font-size:1px;margin:0px auto;width:536px;" role="none" width="536px"><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="g-outlook" style="vertical-align:top;width:0;">
<![endif]--><div class="xc0 ogf g" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="background-color:#fffffe;border:none;vertical-align:top;padding:40px 24px 24px 24px;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td align="center" class="i m" style="font-size:0;padding:0;padding-bottom:16px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border-collapse:collapse;border-spacing:0;"><tbody><tr><td style="width:200px;"> <img alt="" height="auto" src="https://assets.appsmith.com/appsmith-logo-no-margin.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" title="" width="200">
</td></tr></tbody></table>
</td></tr><tr><td class="s m" style="font-size:0;padding:0;padding-bottom:16px;word-break:break-word;"><div style="height:1px;line-height:1px;">&#8202;</div>
</td></tr><tr><td align="center" class="x m" style="font-size:0;padding-bottom:16px;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:20px;font-family:SF Pro Text,Arial,sans-serif;font-weight:600;color:#000000;line-height:24px;">{{inviterFirstName}} has invited you to collaborate on the workspace "{{inviterWorkspaceName}}"</span></p></div>
</td></tr><tr><td align="center" class="x m" style="font-size:0;padding-bottom:16px;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:16px;font-family:SF Pro Text,Arial,sans-serif;font-weight:400;color:#000000;line-height:24px;">You have been invited to join the workspace "{{inviterWorkspaceName}}" in Appsmith. Accept the invite to create your account and get started.</span></p></div>
</td></tr><tr><td class="s m" style="font-size:0;padding:0;padding-bottom:16px;word-break:break-word;"><div style="height:4px;line-height:4px;">&#8202;</div>
</td></tr><tr><td align="center" class="b fw-1" style="font-size:0;padding:0;padding-bottom:0;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border-collapse:separate;width:135px;line-height:100%;"><tbody><tr><td align="center" bgcolor="#f86a2b" role="none" style="border:none;border-radius:4px 4px 4px 4px;cursor:auto;mso-padding-alt:12px 0px 12px 0px;background:#f86a2b;" valign="middle"> <a href="{{primaryLinkUrl}}" style="display:inline-block;width:135px;background:#f86a2b;color:#ffffff;font-family:Inter,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:100%;margin:0;text-decoration:none;text-transform:none;padding:12px 0px 12px 0px;mso-padding-alt:0;border-radius:4px 4px 4px 4px;" target="_blank"> <span style="font-size:14px;font-family:Inter,Arial,sans-serif;font-weight:700;color:#ffffff;line-height:17px;">{{primaryLinkText}}</span></a>
</td></tr></tbody></table>
</td></tr></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<table align="center" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]--><div class="r pr-16 pl-16" style="background:#f8f8f8;background-color:#f8f8f8;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="none" style="background:#f8f8f8;background-color:#f8f8f8;width:100%;"><tbody><tr><td style="border:none;direction:ltr;font-size:0;padding:16px 32px 16px 32px;text-align:left;">
<!--[if mso | IE]>
<table role="none" border="0" cellpadding="0" cellspacing="0"><tr><td class="m-outlook c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf m c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border:none;vertical-align:top;" width="100%"><tbody><tr><td align="center" class="d" style="font-size:0;padding:0;word-break:break-word;"><p style="border-top:solid 1px #cccccc;font-size:1px;margin:0px auto;width:100%;"></p>
<!--[if mso | IE]>
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #cccccc;font-size:1px;margin:0px auto;width:536px;" role="none" width="536px"><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="g-outlook" style="vertical-align:top;width:16px;">
<![endif]--><div class="xc16 ogf g" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="border:none;vertical-align:top;padding:0px 12px 0px 12px;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td align="left" class="i fw-1 m" style="font-size:0;padding:0;padding-bottom:8px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border-collapse:collapse;border-spacing:0;" class="fwm"><tbody><tr><td style="width:512px;" class="fwm"> <img alt="" height="auto" src="https://emailify.b-cdn.net/f76e7fcbed28d8e5b3acc27c5109a06e.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" title="" width="512">
</td></tr></tbody></table>
</td></tr><tr><td align="center" class="x" style="font-size:0;padding-bottom:0;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:14px;font-family:SF Pro Text,Arial,sans-serif;font-weight:400;color:#b3b3b3;line-height:22px;">Appsmith is an open source framework that helps developer and companies to build powerful internal tools.</span></p></div>
</td></tr></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]--></div>
</body>
</html>

View File

@ -1,302 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<!--<![endif]-->
<!--[if (gte mso 9)|(IE)]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if (gte mso 9)|(IE)]>
<style type="text/css">
body {width: 600px;margin: 0 auto;}
table {border-collapse: collapse;}
table, td {mso-table-lspace: 0pt;mso-table-rspace: 0pt;}
img {-ms-interpolation-mode: bicubic;}
</style>
<![endif]-->
<style type="text/css">
body, p, div {
font-family: arial,helvetica,sans-serif;
font-size: 14px;
}
body {
color: #000000;
}
body a {
color: #1188E6;
text-decoration: none;
}
p { margin: 0; padding: 0; }
table.wrapper {
width:100% !important;
table-layout: fixed;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
img.max-width {
max-width: 100% !important;
}
.column.of-2 {
width: 50%;
}
.column.of-3 {
width: 33.333%;
}
.column.of-4 {
width: 25%;
}
ul ul ul ul {
list-style-type: disc !important;
}
ol ol {
list-style-type: lower-roman !important;
}
ol ol ol {
list-style-type: lower-latin !important;
}
ol ol ol ol {
list-style-type: decimal !important;
}
@media screen and (max-width:480px) {
.preheader .rightColumnContent,
.footer .rightColumnContent {
text-align: left !important;
}
.preheader .rightColumnContent div,
.preheader .rightColumnContent span,
.footer .rightColumnContent div,
.footer .rightColumnContent span {
text-align: left !important;
}
.preheader .rightColumnContent,
.preheader .leftColumnContent {
font-size: 80% !important;
padding: 5px 0;
}
table.wrapper-mobile {
width: 100% !important;
table-layout: fixed;
}
img.max-width {
height: auto !important;
max-width: 100% !important;
}
a.bulletproof-button {
display: block !important;
width: auto !important;
font-size: 80%;
padding-left: 0 !important;
padding-right: 0 !important;
}
.columns {
width: 100% !important;
}
.column {
display: block !important;
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
.social-icon-column {
display: inline-block !important;
}
}
</style>
<style>
@media screen and (max-width:480px) {
table\0 {
width: 480px !important;
}
}
</style>
<!--user entered Head Start--><!--End Head user entered-->
</head>
<body>
<center class="wrapper" data-link-color="#1188E6" data-body-style="font-size:14px; font-family:arial,helvetica,sans-serif; color:#000000; background-color:#FFFFFF;">
<div class="webkit">
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#FFFFFF">
<tr>
<td valign="top" bgcolor="#FFFFFF" width="100%">
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="100%">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<!--[if mso]>
<center>
<table><tr><td width="600">
<![endif]-->
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="width:100%; max-width:600px;" align="center">
<tr>
<td role="modules-container" style="padding:0px 0px 0px 0px; color:#000000; text-align:left;" bgcolor="#FFFFFF" width="100%" align="left"><table class="module preheader preheader-hide" role="module" data-type="preheader" border="0" cellpadding="0" cellspacing="0" width="100%" style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
<tr>
<td role="module-content">
<p></p>
</td>
</tr>
</table><table class="module" role="module" data-type="code" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="xmBk5YWrK5pQFNFMyZSx2S">
<tr>
<td height="100%" valign="top" data-role="module-content"><!doctype html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Welcome to Appsmith!</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a{padding:0;}body{margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}table,td{border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;}img{border:0;height:auto;line-height:100%;outline:none;text-decoration:none;-ms-interpolation-mode:bicubic;}p{display:block;margin:0;}
</style>
<!--[if mso]> <noscript><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.ogf{width:100% !important;}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Inter:700" rel="stylesheet" type="text/css">
<style type="text/css">
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:599px){.xc536{width:536px!important;max-width:536px;}.xc0{width:0px!important;max-width:0;}.xc16{width:16px!important;max-width:16px;}}
</style>
<style media="screen and (min-width:599px)">.moz-text-html .xc536{width:536px!important;max-width:536px;}.moz-text-html .xc0{width:0px!important;max-width:0;}.moz-text-html .xc16{width:16px!important;max-width:16px;}
</style>
<style type="text/css">
@media only screen and (max-width:599px){table.fwm{width:100%!important;}td.fwm{width:auto!important;}}
</style>
<style type="text/css">
u+.emailify a,#MessageViewBody a,a[x-apple-data-detectors]{color:inherit!important;text-decoration:none!important;font-size:inherit!important;font-family:inherit!important;font-weight:inherit!important;line-height:inherit!important;}span.MsoHyperlink{mso-style-priority:99;color:inherit;}span.MsoHyperlinkFollowed{mso-style-priority:99;color:inherit;}td.b .klaviyo-image-block{display:inline;vertical-align:middle;}
@media only screen and (max-width:599px){.emailify{height:100%!important;margin:0!important;padding:0!important;width:100%!important;}u+.emailify .glist{margin-left:1em!important;}td.x{padding-left:0!important;padding-right:0!important;}.fwm img{max-width:100%!important;height:auto!important;}td.b.nw>table,td.b.nw a{width:auto!important;}td.stk{border:0!important;}br.sb{display:none!important;}.thd-1 .i-thumbnail{display:inline-block!important;height:auto!important;overflow:hidden!important;}.hd-1{display:block!important;height:auto!important;overflow:visible!important;}.ht-1{display:table!important;height:auto!important;overflow:visible!important;}.hr-1{display:table-row!important;height:auto!important;overflow:visible!important;}.hc-1{display:table-cell!important;height:auto!important;overflow:visible!important;}div.r.pr-16>table>tbody>tr>td,div.r.pr-16>div>table>tbody>tr>td{padding-right:16px!important}div.r.pl-16>table>tbody>tr>td,div.r.pl-16>div>table>tbody>tr>td{padding-left:16px!important}td.b.fw-1>table{width:100%!important}td.fw-1>table>tbody>tr>td>a{display:block!important;width:100%!important;padding-left:0!important;padding-right:0!important;}td.b.fw-1>table{width:100%!important}td.fw-1>table>tbody>tr>td{width:100%!important;padding-left:0!important;padding-right:0!important;}}
</style>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<!--[if gte mso 9]>
<style>li{text-indent:-1em;}
</style>
<![endif]-->
</head>
<body lang="en" class="emailify" style="mso-line-height-rule:exactly;word-spacing:normal;background-color:#d6d6d6;"><div class="bg" style="background-color:#d6d6d6;">
<!--[if mso | IE]>
<table align="center" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]--><div class="r pr-16 pl-16" style="background:#f8f8f8;background-color:#f8f8f8;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="none" style="background:#f8f8f8;background-color:#f8f8f8;width:100%;"><tbody><tr><td style="border:none;direction:ltr;font-size:0;padding:32px 32px 8px 32px;text-align:left;">
<!--[if mso | IE]>
<table role="none" border="0" cellpadding="0" cellspacing="0"><tr><td class="m-outlook c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf m c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border:none;vertical-align:top;" width="100%"><tbody><tr><td align="center" class="d" style="font-size:0;padding:0;word-break:break-word;"><h1 style="border-top:solid 8px #f86a2b;font-size:1px;margin:0px auto;width:100%;"></h1>
<!--[if mso | IE]>
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 8px #f86a2b;font-size:1px;margin:0px auto;width:536px;" role="none" width="536px"><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="g-outlook" style="vertical-align:top;width:0;">
<![endif]--><div class="xc0 ogf g" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="background-color:#fffffe;border:none;vertical-align:top;padding:40px 24px 24px 24px;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td align="center" class="i m" style="font-size:0;padding:0;padding-bottom:16px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border-collapse:collapse;border-spacing:0;"><tbody><tr><td style="width:200px;"> <img alt="" height="auto" src="https://assets.appsmith.com/appsmith-logo-no-margin.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" title="" width="200">
</td></tr></tbody></table>
</td></tr><tr><td class="s m" style="font-size:0;padding:0;padding-bottom:16px;word-break:break-word;"><div style="height:1px;line-height:1px;">&#8202;</div>
</td></tr><tr><td align="center" class="x m" style="font-size:0;padding-bottom:16px;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:20px;font-family:SF Pro Text,Arial,sans-serif;font-weight:600;color:#000000;line-height:24px;">Welcome to Appsmith</span></p></div>
</td></tr><tr><td align="center" class="x m" style="font-size:0;padding-bottom:16px;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:16px;font-family:SF Pro Text,Arial,sans-serif;font-weight:400;color:#000000;line-height:24px;">You're now part of a developer community using appsmith to save hundreds of hours building internal tools! 🎉
</span></p></div></td></tr>
<tr><td align="center" class="x m" style="font-size:0;padding-bottom:16px;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:16px;font-family:SF Pro Text,Arial,sans-serif;font-weight:400;color:#000000;line-height:24px;">If you'd like some help getting started, I'd be happy to get on a call & walk you through building your first app! <a href="http://bit.ly/appsmith-live-demo">Schedule a Call</a> 📅</span></p></div>
</td></tr>
<tr><td align="center" class="x m" style="font-size:0;padding-bottom:16px;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:16px;font-family:SF Pro Text,Arial,sans-serif;font-weight:400;color:#000000;line-height:24px;">We're a fun, active group of developers, and we'd love for you to join us on <a href="http://bit.ly/appsmith-discord">Discord</a></span></p></div>
</td></tr>
<tr><td class="s m" style="font-size:0;padding:0;padding-bottom:16px;word-break:break-word;"><div style="height:4px;line-height:4px;">&#8202;</div>
</td></tr><tr><td align="center" class="b fw-1" style="font-size:0;padding:0;padding-bottom:0;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border-collapse:separate;width:135px;line-height:100%;"><tbody><tr><td align="center" bgcolor="#f86a2b" role="none" style="border:none;border-radius:4px 4px 4px 4px;cursor:auto;mso-padding-alt:12px 0px 12px 0px;background:#f86a2b;" valign="middle"> <a href="{{primaryLinkUrl}}" style="display:inline-block;width:135px;background:#f86a2b;color:#ffffff;font-family:Inter,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:100%;margin:0;text-decoration:none;text-transform:none;padding:12px 0px 12px 0px;mso-padding-alt:0;border-radius:4px 4px 4px 4px;" target="_blank"> <span style="font-size:14px;font-family:Inter,Arial,sans-serif;font-weight:700;color:#ffffff;line-height:17px;">Go To Dashboard</span></a>
</td></tr></tbody></table>
</td></tr></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<table align="center" border="0" cellpadding="0" cellspacing="0" class="r-outlook -outlook pr-16-outlook pl-16-outlook -outlook" role="none" style="width:600px;" width="600" bgcolor="#f8f8f8"><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;">
<![endif]--><div class="r pr-16 pl-16" style="background:#f8f8f8;background-color:#f8f8f8;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="none" style="background:#f8f8f8;background-color:#f8f8f8;width:100%;"><tbody><tr><td style="border:none;direction:ltr;font-size:0;padding:16px 32px 16px 32px;text-align:left;">
<!--[if mso | IE]>
<table role="none" border="0" cellpadding="0" cellspacing="0"><tr><td class="m-outlook c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf m c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border:none;vertical-align:top;" width="100%"><tbody><tr><td align="center" class="d" style="font-size:0;padding:0;word-break:break-word;"><p style="border-top:solid 1px #cccccc;font-size:1px;margin:0px auto;width:100%;"></p>
<!--[if mso | IE]>
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #cccccc;font-size:1px;margin:0px auto;width:536px;" role="none" width="536px"><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="g-outlook" style="vertical-align:top;width:16px;">
<![endif]--><div class="xc16 ogf g" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td><td class="c-outlook -outlook -outlook" style="vertical-align:top;width:536px;">
<![endif]--><div class="xc536 ogf c" style="font-size:0;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td style="border:none;vertical-align:top;padding:0px 12px 0px 12px;">
<table border="0" cellpadding="0" cellspacing="0" role="none" width="100%"><tbody><tr><td align="left" class="i fw-1 m" style="font-size:0;padding:0;padding-bottom:8px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="none" style="border-collapse:collapse;border-spacing:0;" class="fwm"><tbody><tr><td style="width:512px;" class="fwm"> <img alt="" height="auto" src="https://emailify.b-cdn.net/f76e7fcbed28d8e5b3acc27c5109a06e.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" title="" width="512">
</td></tr></tbody></table>
</td></tr><tr><td align="center" class="x" style="font-size:0;padding-bottom:0;word-break:break-word;"><div style="text-align:center;"><p style="Margin:0;text-align:center;"><span style="font-size:14px;font-family:SF Pro Text,Arial,sans-serif;font-weight:400;color:#b3b3b3;line-height:22px;">Appsmith is an open source framework that helps developer and companies to build powerful internal tools.</span></p></div>
</td></tr></tbody></table>
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td></tr></tbody></table></div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]--></div>
</body>
</html></td>
</tr>
</table></td>
</tr>
</table>
<!--[if mso]>
</td>
</tr>
</table>
</center>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</center>
</body>
</html>

View File

@ -55,10 +55,8 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static com.appsmith.server.acl.AclPermission.MANAGE_USERS;
import static com.appsmith.server.acl.AclPermission.RESET_PASSWORD_USERS;
@ -125,43 +123,6 @@ public class UserServiceTest {
public void setup() {
userMono = userService.findByEmail("usertest@usertest.com");
}
// Test if email params are updating correctly
@Test
public void checkEmailParamsForExistingUser() {
Workspace workspace = new Workspace();
workspace.setName("UserServiceTest Update Org");
workspace.setId(UUID.randomUUID().toString());
User inviter = new User();
inviter.setName("inviterUserToApplication");
String inviteUrl = "http://localhost:8080";
String expectedUrl = inviteUrl + "/applications#" + workspace.getId();
Map<String, String> params = userService.getEmailParams(workspace, inviter, inviteUrl, false);
assertEquals(expectedUrl, params.get("primaryLinkUrl"));
assertEquals("inviterUserToApplication", params.get("inviterFirstName"));
assertEquals("UserServiceTest Update Org", params.get("inviterWorkspaceName"));
}
@Test
public void checkEmailParamsForNewUser() {
Workspace workspace = new Workspace();
workspace.setId(UUID.randomUUID().toString());
workspace.setName("UserServiceTest Update Org");
User inviter = new User();
inviter.setName("inviterUserToApplication");
String inviteUrl = "http://localhost:8080";
Map<String, String> params = userService.getEmailParams(workspace, inviter, inviteUrl, true);
assertEquals(inviteUrl, params.get("primaryLinkUrl"));
assertEquals("inviterUserToApplication", params.get("inviterFirstName"));
assertEquals("UserServiceTest Update Org", params.get("inviterWorkspaceName"));
}
// Test the update workspace flow.
@Test
public void updateInvalidUserWithAnything() {
@ -388,9 +349,7 @@ public class UserServiceTest {
signUpUser.setEmail(newUserEmail);
signUpUser.setPassword("123456");
Mono<User> invitedUserSignUpMono = userService
.createUserAndSendEmail(signUpUser, "http://localhost:8080")
.map(UserSignupDTO::getUser);
Mono<User> invitedUserSignUpMono = userService.createUser(signUpUser).map(UserSignupDTO::getUser);
StepVerifier.create(invitedUserSignUpMono)
.assertNext(user -> {
@ -563,24 +522,6 @@ public class UserServiceTest {
.verifyComplete();
}
@Test
public void createUserAndSendEmail_WhenUserExistsWithEmailInOtherCase_ThrowsException() {
User existingUser = new User();
existingUser.setEmail("abcd@gmail.com");
userRepository.save(existingUser).block();
User newUser = new User();
newUser.setEmail("abCd@gmail.com"); // same as above except c in uppercase
newUser.setSource(LoginSource.FORM);
newUser.setPassword("abcdefgh");
Mono<User> userAndSendEmail =
userService.createUserAndSendEmail(newUser, null).map(UserSignupDTO::getUser);
StepVerifier.create(userAndSendEmail)
.expectErrorMessage(AppsmithError.USER_ALREADY_EXISTS_SIGNUP.getMessage(existingUser.getEmail()))
.verify();
}
@Test
public void forgotPasswordTokenGenerate_AfterTrying3TimesIn24Hours_ThrowsException() {
String testEmail = "test-email-for-password-reset";

View File

@ -0,0 +1,192 @@
package com.appsmith.server.services.ce;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.PermissionGroup;
import com.appsmith.server.domains.Tenant;
import com.appsmith.server.domains.TenantConfiguration;
import com.appsmith.server.domains.User;
import com.appsmith.server.domains.Workspace;
import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.services.TenantService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
import static com.appsmith.server.constants.ce.EmailConstantsCE.EMAIL_ROLE_ADMINISTRATOR_TEXT;
import static com.appsmith.server.constants.ce.EmailConstantsCE.FORGOT_PASSWORD_EMAIL_SUBJECT;
import static com.appsmith.server.constants.ce.EmailConstantsCE.FORGOT_PASSWORD_TEMPLATE_CE;
import static com.appsmith.server.constants.ce.EmailConstantsCE.INSTANCE_ADMIN_INVITE_EMAIL_SUBJECT;
import static com.appsmith.server.constants.ce.EmailConstantsCE.INSTANCE_ADMIN_INVITE_EMAIL_TEMPLATE;
import static com.appsmith.server.constants.ce.EmailConstantsCE.INSTANCE_NAME;
import static com.appsmith.server.constants.ce.EmailConstantsCE.INVITER_FIRST_NAME;
import static com.appsmith.server.constants.ce.EmailConstantsCE.INVITER_WORKSPACE_NAME;
import static com.appsmith.server.constants.ce.EmailConstantsCE.PRIMARY_LINK_TEXT;
import static com.appsmith.server.constants.ce.EmailConstantsCE.PRIMARY_LINK_URL;
import static com.appsmith.server.constants.ce.EmailConstantsCE.RESET_URL;
import static com.appsmith.server.constants.ce.EmailConstantsCE.WORKSPACE_EMAIL_SUBJECT_FOR_NEW_USER;
import static graphql.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
@SpringBootTest
@ExtendWith(SpringExtension.class)
class EmailServiceCEImplTest {
@Autowired
TenantService tenantService;
@Mock
EmailSender mockEmailSender;
private EmailServiceCE emailService;
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
TenantConfiguration tenantConfiguration = new TenantConfiguration();
Tenant tenant = tenantService.getDefaultTenant().block();
assert tenant != null;
tenant.setTenantConfiguration(tenantConfiguration);
this.emailService = new EmailServiceCEImpl(mockEmailSender, tenantService);
}
@Test
void sendForgotPasswordEmail() {
String email = "test@example.com";
String resetUrl = "http://example.com/reset";
String originHeader = "http://example.com";
Map<String, String> expectedParams = new HashMap<>();
expectedParams.put(RESET_URL, resetUrl);
expectedParams.put(INSTANCE_NAME, "Appsmith");
doAnswer(invocation -> {
String to = invocation.getArgument(0);
String subject = invocation.getArgument(1);
String text = invocation.getArgument(2);
Map<String, String> params = invocation.getArgument(3);
assertEquals(email, to);
assertEquals(
String.format(FORGOT_PASSWORD_EMAIL_SUBJECT, expectedParams.get(INSTANCE_NAME)), subject);
assertEquals(FORGOT_PASSWORD_TEMPLATE_CE, text);
assertEquals(expectedParams, params);
return Mono.just(true);
})
.when(mockEmailSender)
.sendMail(anyString(), anyString(), anyString(), anyMap());
emailService.sendForgotPasswordEmail(email, resetUrl, originHeader).block();
}
@Test
void sendInviteUserToWorkspaceEmail() {
User invitingUser = new User();
String invitingUserMail = "a@abc.com";
invitingUser.setEmail(invitingUserMail);
User invitedUser = new User();
String invitedUserMail = "b@abc.com";
invitedUser.setEmail(invitedUserMail);
Workspace workspace = new Workspace();
String workspaceName = "testWorkspace";
String workspaceId = "testWorkspaceId";
workspace.setName(workspaceName);
workspace.setId(workspaceId);
PermissionGroup permissionGroup = new PermissionGroup();
String permissionGroupName = FieldName.ADMINISTRATOR + "role";
permissionGroup.setName(permissionGroupName);
boolean isNewUser = true;
String originHeader = "http://example.com";
Map<String, String> expectedParams = new HashMap<>();
expectedParams.put(INVITER_WORKSPACE_NAME, workspaceName);
expectedParams.put(INVITER_FIRST_NAME, invitingUserMail);
expectedParams.put(FieldName.ROLE, permissionGroupName);
expectedParams.put(INSTANCE_NAME, "Appsmith");
doAnswer(invocation -> {
String to = invocation.getArgument(0);
String subject = invocation.getArgument(1);
String text = invocation.getArgument(2);
Map<String, String> params = invocation.getArgument(3);
assertEquals(invitedUser.getEmail(), to);
assertEquals(String.format(WORKSPACE_EMAIL_SUBJECT_FOR_NEW_USER, workspace.getName()), subject);
assertTrue(params.containsKey(PRIMARY_LINK_URL));
assertTrue(params.containsKey(PRIMARY_LINK_TEXT));
assertEquals(expectedParams.get(INSTANCE_NAME), params.get(INSTANCE_NAME));
assertEquals(expectedParams.get(INVITER_WORKSPACE_NAME), params.get(INVITER_WORKSPACE_NAME));
assertEquals(expectedParams.get(INVITER_FIRST_NAME), params.get(INVITER_FIRST_NAME));
assertEquals(EMAIL_ROLE_ADMINISTRATOR_TEXT, params.get(FieldName.ROLE));
return Mono.just(true);
})
.when(mockEmailSender)
.sendMail(anyString(), anyString(), anyString(), anyMap());
emailService
.sendInviteUserToWorkspaceEmail(
invitingUser, invitedUser, workspace, permissionGroup, originHeader, isNewUser)
.block();
}
@Test
void testSendInstanceAdminInviteEmail() {
User invitingUser = new User();
String invitingUserMail = "a@abc.com";
invitingUser.setEmail(invitingUserMail);
User invitedUser = new User();
String invitedUserMail = "b@abc.com";
invitedUser.setEmail(invitedUserMail);
String originHeader = "http://example.com";
Map<String, String> expectedParams = new HashMap<>();
expectedParams.put(INSTANCE_NAME, "Appsmith");
expectedParams.put(INVITER_FIRST_NAME, invitingUserMail);
doAnswer(invocation -> {
String to = invocation.getArgument(0);
String subject = invocation.getArgument(1);
String text = invocation.getArgument(2);
Map<String, String> params = invocation.getArgument(3);
assertEquals(invitedUser.getEmail(), to);
assertEquals(
String.format(INSTANCE_ADMIN_INVITE_EMAIL_SUBJECT, expectedParams.get(INSTANCE_NAME)),
subject);
assertEquals(INSTANCE_ADMIN_INVITE_EMAIL_TEMPLATE, text);
assertEquals(expectedParams.get(INVITER_FIRST_NAME), params.get(INVITER_FIRST_NAME));
assertTrue(params.containsKey(PRIMARY_LINK_URL));
assertTrue(params.containsKey(PRIMARY_LINK_TEXT));
return Mono.just(true);
})
.when(mockEmailSender)
.sendMail(anyString(), anyString(), anyString(), anyMap());
emailService
.sendInstanceAdminInviteEmail(invitedUser, invitingUser, originHeader, true)
.block();
}
}

View File

@ -12,6 +12,7 @@ import com.appsmith.server.notifications.EmailSender;
import com.appsmith.server.repositories.UserRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.TenantService;
@ -90,6 +91,9 @@ public class EnvManagerTest {
@MockBean
private ObjectMapper objectMapper;
@MockBean
private EmailService emailService;
@BeforeEach
public void setup() {
envManager = new EnvManagerImpl(
@ -107,7 +111,8 @@ public class EnvManagerTest {
configService,
userUtils,
tenantService,
objectMapper);
objectMapper,
emailService);
}
@Test

View File

@ -10,6 +10,7 @@ import com.appsmith.server.helpers.UserUtils;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.CaptchaService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.EmailService;
import com.appsmith.server.services.UserDataService;
import com.appsmith.server.services.UserService;
import org.junit.jupiter.api.BeforeEach;
@ -57,6 +58,9 @@ public class UserSignupTest {
@MockBean
private NetworkUtils networkUtils;
@MockBean
private EmailService emailService;
private UserSignup userSignup;
@BeforeEach
@ -71,7 +75,8 @@ public class UserSignupTest {
envManager,
commonConfig,
userUtils,
networkUtils);
networkUtils,
emailService);
}
private String createRandomString(int length) {