diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..bfae5a130d974eb9a2fc4fe67ef8dad0ba8f48d8
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,10 @@
+FROM maven:3.8.4-openjdk-8-slim AS build
+
+WORKDIR /app
+
+COPY pom.xml .
+COPY src ./src
+
+RUN mvn clean package -DskipTests
+
+CMD ["java", "-jar", "target/soap-0.0.jar"]
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index a1da0d2f58152f42020f11d530eb45fe947dc135..43abf344115ba9a736c857ec7564c7d260cf0c85 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,9 +1,14 @@
 version: "3.1"
 
 services:
-  db:
+  mysql:
     image: mysql:8.2
     restart: always
+    healthcheck:
+      test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
+      timeout: 12s
+      retries: 10
+      interval: 1s
     environment:
       MYSQL_DATABASE: 'db'
       MYSQL_USER: 'user'
@@ -13,5 +18,27 @@ services:
       - '3306:3306'
     volumes:
       - my-db:/var/lib/mysql
+    networks:
+      - all-bridge
+  soap-service:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    environment:
+      REST_API_KEY: "REST_API_KEY"
+      MONOLITH_API_KEY: "MONOLITH_API_KEY"
+      sender_email: kms.bizzzzzz@gmail.com
+      sender_password:
+    depends_on:
+      mysql:
+        condition: service_healthy
+    ports:
+      - "8080:8080"
+    networks:
+      - all-bridge
+
 volumes:
-  my-db:
\ No newline at end of file
+  my-db:
+networks:
+  all-bridge:
+    driver: bridge
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 8ba5d3047f79443d7ce8f83dfa73a7adb5cd038d..19ba5675fdd3a947c84e78ab36b342c009692c08 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,8 +12,8 @@
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <maven.compiler.source>21</maven.compiler.source>
-    <maven.compiler.target>21</maven.compiler.target>
+    <maven.compiler.source>8</maven.compiler.source>
+    <maven.compiler.target>8</maven.compiler.target>
   </properties>
 
   <dependencies>
@@ -27,91 +27,50 @@
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.30</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.sun.xml.ws</groupId>
-      <artifactId>jaxws-rt</artifactId>
-      <version>2.3.6</version>
     </dependency>
     <dependency>
       <groupId>com.j256.ormlite</groupId>
       <artifactId>ormlite-jdbc</artifactId>
       <version>6.1</version>
     </dependency>
-    <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
     <dependency>
       <groupId>com.mysql</groupId>
       <artifactId>mysql-connector-j</artifactId>
       <version>8.2.0</version>
     </dependency>
-
+    <dependency>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>javax.mail</artifactId>
+    <version>1.6.2</version>
+  </dependency>
+    <dependency>
+      <groupId>org.freemarker</groupId>
+      <artifactId>freemarker</artifactId>
+      <version>2.3.30</version>
+    </dependency>
   </dependencies>
 
   <build>
-    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
-      <plugins>
-        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
-        <plugin>
-          <artifactId>maven-clean-plugin</artifactId>
-          <version>3.1.0</version>
-        </plugin>
-        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
-        <plugin>
-          <artifactId>maven-resources-plugin</artifactId>
-          <version>3.0.2</version>
-        </plugin>
-        <plugin>
-          <artifactId>maven-compiler-plugin</artifactId>
-          <version>3.8.0</version>
-        </plugin>
-        <plugin>
-          <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.22.1</version>
-        </plugin>
-        <plugin>
-          <artifactId>maven-jar-plugin</artifactId>
-          <version>3.0.2</version>
-        </plugin>
-        <plugin>
-          <artifactId>maven-install-plugin</artifactId>
-          <version>2.5.2</version>
-        </plugin>
-        <plugin>
-          <artifactId>maven-deploy-plugin</artifactId>
-          <version>2.8.2</version>
-        </plugin>
-        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
-        <plugin>
-          <artifactId>maven-site-plugin</artifactId>
-          <version>3.7.1</version>
-        </plugin>
-        <plugin>
-          <artifactId>maven-project-info-reports-plugin</artifactId>
-          <version>3.0.0</version>
-        </plugin>
-        <plugin>
-          <groupId>org.jvnet.jax-ws-commons</groupId>
-          <artifactId>jaxws-maven-plugin</artifactId>
-          <version>2.2</version>
-          <executions>
-            <execution>
-              <id>SomeId</id>
-              <goals>
-                <goal>wsgen</goal>
-              </goals>
-              <phase>prepare-package</phase>
-              <configuration>
-                <sei>com.kms.service.PaymentService</sei>
-                <genWsdl>true</genWsdl>
-                <keep>true</keep>
-                <inlineSchemas>true</inlineSchemas>
-              </configuration>
-            </execution>
-          </executions>
-        </plugin>
-
-      </plugins>
-    </pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <configuration>
+          <transformers>
+            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+              <mainClass>com.kms.App</mainClass>
+            </transformer>
+          </transformers>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
   </build>
 </project>
diff --git a/src/main/java/com/kms/App.java b/src/main/java/com/kms/App.java
index 1b28d905343e33e24cfcf13c2dc24dd844f809d1..94732180675731af0c8838d688c18bd3da1fdbfa 100644
--- a/src/main/java/com/kms/App.java
+++ b/src/main/java/com/kms/App.java
@@ -4,6 +4,7 @@ import com.j256.ormlite.dao.Dao;
 import com.j256.ormlite.dao.DaoManager;
 import com.j256.ormlite.jdbc.JdbcConnectionSource;
 import com.j256.ormlite.support.ConnectionSource;
+import com.j256.ormlite.table.TableUtils;
 import com.kms.crosscut.LoggingHelper;
 import com.kms.model.Log;
 import com.kms.model.Payment;
@@ -15,20 +16,29 @@ import javax.xml.ws.Endpoint;
 import java.sql.SQLException;
 import java.util.Timer;
 
+
 public class App {
-    public static void main( String[] args ) throws SQLException {
-        String databaseUrl = "jdbc:mysql://localhost:3306/db?user=user&password=password";
-        ConnectionSource connectionSource = new JdbcConnectionSource(databaseUrl);
-        Dao<Payment,Integer> paymentDao =
-                DaoManager.createDao(connectionSource, Payment.class);
-        Dao<Log,Integer> logDao = DaoManager.createDao(connectionSource, Log.class);
-        LoggingHelper.init(logDao);
-
-        Timer time = new Timer();
-        PaymentCompletorTask task = new PaymentCompletorTask(paymentDao);
-        time.schedule(task, 0, 25000);
-
-        Endpoint.publish("http://localhost:8080/paymentservice", new PaymentService(paymentDao));
-        Endpoint.publish("http://localhost:8080/paymenthistory", new PaymentHistoryService(paymentDao));
+    public static void main( String[] args ) {
+        try {
+            final String databaseUrl = "jdbc:mysql://mysql:3306/db?user=user&password=password";
+            final ConnectionSource connectionSource = new JdbcConnectionSource(databaseUrl);
+            final Dao<Payment,Integer> paymentDao = DaoManager.createDao(connectionSource, Payment.class);
+            final Dao<Log,Integer> logDao = DaoManager.createDao(connectionSource, Log.class);
+            LoggingHelper.init(logDao);
+
+            TableUtils.dropTable(connectionSource, Payment.class, true);
+            TableUtils.dropTable(connectionSource, Log.class, true);
+
+            TableUtils.createTableIfNotExists(connectionSource, Payment.class);
+            TableUtils.createTableIfNotExists(connectionSource, Log.class);
+
+            final Timer time = new Timer();
+            PaymentCompletorTask task = new PaymentCompletorTask(paymentDao);
+            time.schedule(task, 0, 25000);
+
+            Endpoint.publish("http://0.0.0.0:8080/paymentservice", new PaymentService(paymentDao));
+            Endpoint.publish("http://0.0.0.0:8080/paymenthistory", new PaymentHistoryService(paymentDao));
+        } catch (SQLException e) {
+        }
     }
 }
diff --git a/src/main/java/com/kms/crosscut/LoggingHelper.java b/src/main/java/com/kms/crosscut/LoggingHelper.java
index f5ee1b1c8950141c6dcdf6871d5d4f748e1c5e70..7fc6181538c1741865da2752e50ce764356b02d5 100644
--- a/src/main/java/com/kms/crosscut/LoggingHelper.java
+++ b/src/main/java/com/kms/crosscut/LoggingHelper.java
@@ -23,18 +23,19 @@ public class LoggingHelper {
         return instance;
     }
 
-    public void log(String description, String ip, String endpoint) {
-        Log log = Log.builder()
+    public void log(String description, String ip, String endpoint, String callerId, String annotation) {
+        final Log log = Log.builder()
                 .endpoint(endpoint)
                 .originIp(ip)
                 .requestDesc(description)
                 .requestTime(new Timestamp(System.currentTimeMillis()))
+                .callerId(callerId)
+                .annotation(annotation)
                 .build();
 
         try {
             logDao.create(log);
         } catch (SQLException ignored) {
-            System.out.println(ignored);
         }
     }
 
diff --git a/src/main/java/com/kms/dto/EmailReq.java b/src/main/java/com/kms/dto/EmailReq.java
index bb417f5664f9f9f4494351060eabee0f351db0aa..5d7527bd9fe3505432b90597bb10d1c5c80a8dbd 100644
--- a/src/main/java/com/kms/dto/EmailReq.java
+++ b/src/main/java/com/kms/dto/EmailReq.java
@@ -13,7 +13,5 @@ public class EmailReq {
 
     private String recipient;
     private String cc;
-    private String subject;
-    private String body;
 
 }
diff --git a/src/main/java/com/kms/dto/PaymentHistoryResp.java b/src/main/java/com/kms/dto/PaymentHistoryResp.java
index 754d58650fb4ab51b59aa102370c6be675d81021..983b680318e8d46b8c905a9f19f8456c4050ddf9 100644
--- a/src/main/java/com/kms/dto/PaymentHistoryResp.java
+++ b/src/main/java/com/kms/dto/PaymentHistoryResp.java
@@ -5,8 +5,6 @@ import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 
-import java.sql.Timestamp;
-
 @Getter
 @Setter
 @AllArgsConstructor
diff --git a/src/main/java/com/kms/dto/PaymentInitReq.java b/src/main/java/com/kms/dto/PaymentInitReq.java
index 2bf3707b8a5d1a7b5badd35eb3d9cfe1e75484f0..5fea4af57a6e2520e91711e14494f9f3fe6b9d2c 100644
--- a/src/main/java/com/kms/dto/PaymentInitReq.java
+++ b/src/main/java/com/kms/dto/PaymentInitReq.java
@@ -5,17 +5,15 @@ import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
 
 @Getter @Setter
 @AllArgsConstructor
 @NoArgsConstructor
-@XmlRootElement
 public class PaymentInitReq {
 
     private double amount;
     private String description;
     private String initiatorId;
+    private String idempotentId;
 
 }
diff --git a/src/main/java/com/kms/dto/PaymentInitResp.java b/src/main/java/com/kms/dto/PaymentInitResp.java
index e9e18c3750839fd5255b4b928f46f0e95e494a52..32aca6f496b1984fd3fd48dd388f9c9fa111ac2e 100644
--- a/src/main/java/com/kms/dto/PaymentInitResp.java
+++ b/src/main/java/com/kms/dto/PaymentInitResp.java
@@ -13,5 +13,7 @@ public class PaymentInitResp {
 
     private Integer paymentId;
     private boolean success;
+    private int responseCode; // 1 for success, 2 for unexpected error, 3 for invalid idempotency key
+    private String annotation;
 
 }
diff --git a/src/main/java/com/kms/dto/PaymentStatusResp.java b/src/main/java/com/kms/dto/PaymentStatusResp.java
index d24d0cdc16ad5762cb338e1cf93ccef5cf1f123f..4e0fa895e358d0ed7c8418002ec1952d57f29963 100644
--- a/src/main/java/com/kms/dto/PaymentStatusResp.java
+++ b/src/main/java/com/kms/dto/PaymentStatusResp.java
@@ -5,7 +5,6 @@ import lombok.Builder;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
-
 @Getter
 @Setter
 @AllArgsConstructor
diff --git a/src/main/java/com/kms/handler/AuthHandler.java b/src/main/java/com/kms/handler/AuthHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f57cabab3e835ad972de1f82744f89d80d9fab2
--- /dev/null
+++ b/src/main/java/com/kms/handler/AuthHandler.java
@@ -0,0 +1,59 @@
+package com.kms.handler;
+
+import javax.xml.namespace.QName;
+import javax.xml.soap.SOAPElement;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPHeader;
+import javax.xml.ws.handler.MessageContext;
+import javax.xml.ws.handler.soap.SOAPHandler;
+import javax.xml.ws.handler.soap.SOAPMessageContext;
+import java.util.Iterator;
+import java.util.Set;
+
+public class AuthHandler implements SOAPHandler<SOAPMessageContext>  {
+
+    private static final String REST_API_KEY = System.getenv("REST_API_KEY");
+    private static final String MONOLITH_API_KEY = System.getenv("MONOLITH_API_KEY");
+    private static final String NAMESPACE = "http://service.kms.com/";
+
+    @Override
+    public Set<QName> getHeaders() {
+        return null;
+    }
+
+    @Override
+    public boolean handleMessage(SOAPMessageContext soapMessageContext) {
+        try {
+            final SOAPHeader header = soapMessageContext.getMessage().getSOAPHeader();
+            final QName apiKeyQName = new QName(NAMESPACE, "apiKey");
+            String apiKey = "";
+            final Iterator x = header.getChildElements(apiKeyQName);
+            if (x.hasNext()) {
+                SOAPElement apiKeyElement = (SOAPElement) x.next();
+                apiKey = apiKeyElement.getValue();
+            }
+
+            String callerId = "null";
+            if (apiKey.equals(REST_API_KEY)) {
+                callerId = "REST";
+            }else if (apiKey.equals(MONOLITH_API_KEY)) {
+                callerId = "MONOLITH";
+            }
+
+            soapMessageContext.put("callerId", callerId);
+        } catch (SOAPException e) {
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean handleFault(SOAPMessageContext soapMessageContext) {
+        return false;
+    }
+
+    @Override
+    public void close(MessageContext messageContext) {
+
+    }
+}
diff --git a/src/main/java/com/kms/handler/LogHandler.java b/src/main/java/com/kms/handler/LogHandler.java
index d9d5a495863506f38db0e5cadbd7944fced2f8f9..8975be10d24311c9b24dc62070e1211fab1466c2 100644
--- a/src/main/java/com/kms/handler/LogHandler.java
+++ b/src/main/java/com/kms/handler/LogHandler.java
@@ -2,14 +2,16 @@ package com.kms.handler;
 
 import com.kms.crosscut.LoggingHelper;
 import com.sun.net.httpserver.HttpExchange;
-import com.sun.xml.ws.developer.JAXWSProperties;
 
 import javax.xml.namespace.QName;
 import javax.xml.soap.SOAPBody;
 import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPFactory;
+import javax.xml.soap.SOAPFault;
 import javax.xml.ws.handler.MessageContext;
 import javax.xml.ws.handler.soap.SOAPHandler;
 import javax.xml.ws.handler.soap.SOAPMessageContext;
+import javax.xml.ws.soap.SOAPFaultException;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -18,6 +20,16 @@ import java.util.Set;
 
 public class LogHandler implements SOAPHandler<SOAPMessageContext> {
 
+    private static final SOAPFactory soapFactory;
+
+    static {
+        try {
+            soapFactory = SOAPFactory.newInstance();
+        } catch (SOAPException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     @Override
     public Set<QName> getHeaders() {
         return null;
@@ -25,7 +37,8 @@ public class LogHandler implements SOAPHandler<SOAPMessageContext> {
 
     @Override
     public boolean handleMessage(SOAPMessageContext soapMessageContext) {
-        boolean messageHandled = (boolean) soapMessageContext.getOrDefault("messageHandled", false);
+        final boolean messageHandled =
+                (boolean) soapMessageContext.getOrDefault("messageHandled", false);
 
         if (messageHandled) {
             return true;
@@ -33,19 +46,31 @@ public class LogHandler implements SOAPHandler<SOAPMessageContext> {
 
         soapMessageContext.put("messageHandled", true);
 
-        HttpExchange exchange = (HttpExchange)soapMessageContext.get(JAXWSProperties.HTTP_EXCHANGE);
+        HttpExchange exchange = (HttpExchange)soapMessageContext.get("com.sun.xml.internal.ws.http.exchange");
         InetSocketAddress remoteAddress = exchange.getRemoteAddress();
-        String remoteHost = remoteAddress.getHostName();
-
+        final String address = String.valueOf(remoteAddress.getAddress());
         try {
             SOAPBody soapBody = soapMessageContext.getMessage().getSOAPBody();
             OutputStream outputStream = new ByteArrayOutputStream();
             soapMessageContext.getMessage().writeTo(outputStream);
+            final String callerId = (String) soapMessageContext.getOrDefault("callerId", "null");
+
+            final LoggingHelper loggingHelper = LoggingHelper.getInstance();
+            if (callerId.equals("null")) {
+                SOAPFault customFault = soapFactory.createFault();
+                customFault.setFaultCode("InvalidRequest");
+                customFault.setFaultString("API key is invalid.");
+                loggingHelper.log(outputStream.toString(), address,
+                        soapBody.getChildNodes().item(1).getNodeName(), callerId, "invalid api key");
 
-            LoggingHelper loggingHelper = LoggingHelper.getInstance();
-            loggingHelper.log(outputStream.toString(), remoteHost, soapBody.getChildNodes().item(1).getNodeName());
+                throw new SOAPFaultException(customFault);
+            }
+
+            loggingHelper.log(outputStream.toString(), address,
+                    soapBody.getChildNodes().item(1).getNodeName(), callerId, "");
         } catch (SOAPException | IOException ignored) {
         }
+
         return true;
     }
 
diff --git a/src/main/java/com/kms/model/Log.java b/src/main/java/com/kms/model/Log.java
index a2e683d3cadd4bd22382e8678d1c3528f27d5c5f..539bc7b853a2b2abb0cfeb1a9b01668842b728b4 100644
--- a/src/main/java/com/kms/model/Log.java
+++ b/src/main/java/com/kms/model/Log.java
@@ -10,6 +10,8 @@ import lombok.Setter;
 
 import java.sql.Timestamp;
 
+import static java.time.LocalTime.now;
+
 @AllArgsConstructor
 @Getter
 @Setter
@@ -22,13 +24,15 @@ public class Log {
     private Integer id;
     @DatabaseField(columnName = "request_description")
     private String requestDesc;
-    @DatabaseField(columnName = "origin_ip")
+    @DatabaseField(columnName = "origin_ip", canBeNull = false)
     private String originIp;
-    @DatabaseField(columnName = "endpoint")
+    @DatabaseField(columnName = "endpoint", canBeNull = false)
     private String endpoint;
-    @DatabaseField(columnName = "request_time")
+    @DatabaseField(columnName = "request_time", columnDefinition = "TIMESTAMP DEFAULT NOW() NOT NULL")
     private Timestamp requestTime;
-    @DatabaseField(columnName = "caller_id")
+    @DatabaseField(columnName = "caller_id", canBeNull = false)
     private String callerId;
+    @DatabaseField(columnName = "annotation")
+    private String annotation;
 
 }
diff --git a/src/main/java/com/kms/model/Payment.java b/src/main/java/com/kms/model/Payment.java
index 89fba01d3a284a5b3dafd177a06748ca5b97b908..b766dc03f97d9e6eab527c080913e0462c6a3139 100644
--- a/src/main/java/com/kms/model/Payment.java
+++ b/src/main/java/com/kms/model/Payment.java
@@ -19,18 +19,20 @@ public class Payment {
 
     @DatabaseField(generatedId = true, columnName = "payment_id")
     private Integer paymentId;
-    @DatabaseField(columnName = "amount")
+    @DatabaseField(columnName = "amount", canBeNull = false)
     private double amount;
-    @DatabaseField(columnName = "payment_init_time")
+    @DatabaseField(columnName = "payment_init_time", canBeNull = false,
+            columnDefinition = "TIMESTAMP DEFAULT NOW() NOT NULL")
     private Timestamp paymentInitTime;
-    @DatabaseField(columnName = "status_paid")
+    @DatabaseField(columnName = "status_paid", defaultValue = "0", canBeNull = false)
     private boolean paid;
     @DatabaseField(columnName = "payment_paid_time")
     private Timestamp paymentPaidTime;
     @DatabaseField(columnName = "description")
     private String description;
-    @DatabaseField(columnName = "initiator_id")
+    @DatabaseField(columnName = "initiator_id", canBeNull = false)
     private String initiatorId;
-
+    @DatabaseField(columnName = "idempotent_id", canBeNull = false)
+    private String idempotentId;
 
 }
diff --git a/src/main/java/com/kms/service/EmailService.java b/src/main/java/com/kms/service/EmailService.java
index fbf023487737e499034375f243869d491c42c750..7d02a57eb990f7f17b0ea49ebe7ee73d78223f74 100644
--- a/src/main/java/com/kms/service/EmailService.java
+++ b/src/main/java/com/kms/service/EmailService.java
@@ -2,21 +2,79 @@ package com.kms.service;
 
 import com.kms.util.EmailUtil;
 
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import java.util.Properties;
+
 public class EmailService {
 
-    public static void send(String recipient, String cc, String subject, String body) {
-        if (recipient == null || recipient.isEmpty() || !EmailUtil.isEmailValid(recipient)) {
+    private static final Session session;
+    private static final Properties properties;
+    private static final String SENDER_EMAIL;
+    private static final String SENDER_PASSWORD;
+
+    static {
+        properties = new Properties();
+        properties.put("mail.smtp.host", "smtp.gmail.com");
+        properties.put("mail.smtp.socketFactory.port", "465");
+        properties.put("mail.smtp.socketFactory.class",
+                "javax.net.ssl.SSLSocketFactory");
+        properties.put("mail.smtp.auth", "true");
+        properties.put("mail.smtp.port", "465");
+
+        SENDER_EMAIL = System.getenv("sender_email");
+        SENDER_PASSWORD = System.getenv("sender_password");
+        session = Session.getDefaultInstance(properties,
+                new javax.mail.Authenticator() {
+                    protected PasswordAuthentication getPasswordAuthentication() {
+                        return new PasswordAuthentication(SENDER_EMAIL,SENDER_PASSWORD);
+                    }
+                });
+    }
+
+
+    public static void send(String recipient, String cc, String subject, String body){
+        if (recipient == null || recipient.isEmpty()) {
             return;
         }
 
-        for (String x: cc.split(",")) {
-            x = x.trim();
-            if (!EmailUtil.isEmailValid(x)) {
+        try {
+            MimeMessage message = new MimeMessage(session);
+
+            int toCount = 0;
+            for (String email: recipient.split(",")) {
+                email = email.trim();
+                if (!EmailUtil.isEmailValid(email)) {
+                    continue;
+                }
+                ++toCount;
+                message.addRecipient(Message.RecipientType.TO,new InternetAddress(email));
+            }
+            if (toCount == 0) {
                 return;
             }
-        }
 
-        // TODO: send with smtp
+            for (String email: cc.split(",")) {
+                email = email.trim();
+                if (!EmailUtil.isEmailValid(email)) {
+                    continue;
+                }
+                message.addRecipient(Message.RecipientType.CC, new InternetAddress(email));
+            }
+            message.setSubject(subject);
+            message.setContent(body, "text/html");
+
+            Transport.send(message);
+
+            System.out.println("email sent to: " + recipient);
+        } catch (MessagingException e) {
+            System.out.println(e.getMessage());
+        }
     }
 
 }
diff --git a/src/main/java/com/kms/service/PaymentHistoryService.java b/src/main/java/com/kms/service/PaymentHistoryService.java
index 81fd853108f5a7c874177723fa03297e8ea4870c..b29db99e5aa23c9586618e0e2e15ed6788a6f180 100644
--- a/src/main/java/com/kms/service/PaymentHistoryService.java
+++ b/src/main/java/com/kms/service/PaymentHistoryService.java
@@ -5,14 +5,23 @@ import com.kms.dto.EmailReq;
 import com.kms.dto.PaymentHistoryResp;
 
 import com.kms.model.Payment;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
 import lombok.RequiredArgsConstructor;
 
 import javax.jws.HandlerChain;
 import javax.jws.WebMethod;
 import javax.jws.WebParam;
 import javax.jws.WebService;
+import java.io.IOException;
+import java.io.StringWriter;
 import java.sql.SQLException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 @WebService
 @RequiredArgsConstructor
@@ -21,6 +30,14 @@ public class PaymentHistoryService {
 
     private final Dao<Payment,Integer> paymentDao;
 
+    private static final Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
+
+    static {
+        cfg.setClassForTemplateLoading(PaymentHistoryService.class, "/");
+        cfg.setDefaultEncoding("UTF-8");
+        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+    }
+
     @WebMethod
     public List<PaymentHistoryResp> getPaymentHistory(@WebParam(name = "initiatorId") String initiatorId,
                                                       @WebParam(name = "sendMail") boolean sendMail,
@@ -33,16 +50,28 @@ public class PaymentHistoryService {
             List<Payment> payments = paymentDao.queryForMatching(filter);
 
             if (sendMail && emailReq != null) {
-                EmailService.send(emailReq.getRecipient(), emailReq.getCc(), emailReq.getSubject(), emailReq.getBody());
+                EmailService.send(emailReq.getRecipient(), emailReq.getCc(),
+                        "KMS Payment History", formHistoryMailBody(payments));
+
             }
 
             return payments.stream()
                     .map(el -> new PaymentHistoryResp(el.getPaymentInitTime().toString(), el.getPaymentPaidTime().toString(),
                                     el.getAmount(), el.getDescription()))
-                    .toList();
-        } catch (SQLException e) {
+                    .collect(Collectors.toList());
+        } catch (SQLException | TemplateException | IOException e) {
             throw new RuntimeException(e);
         }
     }
 
+    private String formHistoryMailBody(List<Payment> payments) throws IOException, TemplateException {
+        Template template = cfg.getTemplate("history.ftl");
+        Map<String, Object> dataModel = new HashMap<>();
+        dataModel.put("payments", payments);
+
+        StringWriter stringWriter = new StringWriter();
+        template.process(dataModel, stringWriter);
+        return stringWriter.toString();
+    }
+
 }
diff --git a/src/main/java/com/kms/service/PaymentService.java b/src/main/java/com/kms/service/PaymentService.java
index 1ebe4e5eae23885c1c6cdacf7bf8e69338b41042..a02e9300ca5dbcd802765ef38f5f66db812b570b 100644
--- a/src/main/java/com/kms/service/PaymentService.java
+++ b/src/main/java/com/kms/service/PaymentService.java
@@ -7,13 +7,13 @@ import com.kms.dto.PaymentStatusResp;
 import com.kms.model.Payment;
 import lombok.RequiredArgsConstructor;
 
-import javax.annotation.Resource;
 import javax.jws.HandlerChain;
 import javax.jws.WebMethod;
 import javax.jws.WebParam;
 import javax.jws.WebService;
 import java.sql.SQLException;
 import java.sql.Timestamp;
+import java.util.List;
 
 @WebService
 @HandlerChain(file = "handlers.xml")
@@ -22,23 +22,35 @@ public class PaymentService {
 
     private final Dao<Payment,Integer> paymentDao;
 
-
     @WebMethod
     public PaymentInitResp initPayment(@WebParam(name = "req") PaymentInitReq req) {
-        Payment payment = Payment.builder()
-                .amount(req.getAmount())
-                .paymentInitTime(new Timestamp(System.currentTimeMillis()))
-                .initiatorId(req.getInitiatorId())
-                .description(req.getDescription())
-                .build();
-
         try {
+            List<Payment> idempotentCheck = paymentDao.queryForMatching(Payment.builder()
+                    .initiatorId(req.getInitiatorId())
+                    .idempotentId(req.getIdempotentId()).build());
+
+            if (!idempotentCheck.isEmpty()) {
+                Payment payment = idempotentCheck.get(0);
+                if (payment.getAmount() != req.getAmount() || payment.isPaid()) {
+                   return new PaymentInitResp(null, false, 3,
+                           "You're trying to create another payment request with duplicate idempotency key");
+                }
+                return new PaymentInitResp(payment.getPaymentId(), true, 1, "");
+            }
+
+            Payment payment = Payment.builder()
+                    .amount(req.getAmount())
+                    .paymentInitTime(new Timestamp(System.currentTimeMillis()))
+                    .initiatorId(req.getInitiatorId())
+                    .idempotentId(req.getIdempotentId())
+                    .description(req.getDescription())
+                    .build();
+
             paymentDao.create(payment);
+            return new PaymentInitResp(payment.getPaymentId(), true, 1, "");
         } catch (SQLException e) {
-            return new PaymentInitResp(null, false);
+            return new PaymentInitResp(null, false, 2, "Unexpected error has happened");
         }
-
-        return new PaymentInitResp(payment.getPaymentId(), true);
     }
 
     @WebMethod
@@ -49,7 +61,6 @@ public class PaymentService {
             if (payment == null) {
                 return new PaymentStatusResp(null, "Payment id not found");
             }
-
             return new PaymentStatusResp(payment.isPaid(), null);
         } catch (SQLException e) {
             return new PaymentStatusResp(null, "Unexpected error has happened");
diff --git a/src/main/java/com/kms/task/PaymentCompletorTask.java b/src/main/java/com/kms/task/PaymentCompletorTask.java
index 592a2f94b181389668a0b8be502cfd1b707b4cf8..f41207dff0d60f7f42f4f5c206c04e9d8bba7c17 100644
--- a/src/main/java/com/kms/task/PaymentCompletorTask.java
+++ b/src/main/java/com/kms/task/PaymentCompletorTask.java
@@ -4,6 +4,7 @@ import com.j256.ormlite.dao.Dao;
 import com.kms.model.Payment;
 
 import java.sql.SQLException;
+import java.sql.Timestamp;
 import java.util.List;
 import java.util.TimerTask;
 
@@ -18,10 +19,11 @@ public class PaymentCompletorTask extends TimerTask {
     @Override
     public void run() {
         try {
-            List<Payment> paymentsNotDone = paymentDao.queryForEq("status_paid", 0);
+            final List<Payment> paymentsNotDone = paymentDao.queryForEq("status_paid", 0);
 
             for (Payment p: paymentsNotDone) {
                 p.setPaid(true);
+                p.setPaymentPaidTime(new Timestamp(System.currentTimeMillis()));
                 paymentDao.update(p);
             }
         } catch (SQLException e) {
diff --git a/src/main/java/com/kms/task/PdfCleanerTask.java b/src/main/java/com/kms/task/PdfCleanerTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e4833405abff7fc346f4fa1912f189c7059d2e1
--- /dev/null
+++ b/src/main/java/com/kms/task/PdfCleanerTask.java
@@ -0,0 +1,13 @@
+package com.kms.task;
+
+import java.util.TimerTask;
+
+public class PdfCleanerTask extends TimerTask {
+
+    private static final String PDF_FILE_LOCATION = System.getenv("PDF_FILE_LOCATION");
+
+    @Override
+    public void run() {
+
+    }
+}
diff --git a/src/main/resources/handlers.xml b/src/main/resources/handlers.xml
index 033b4d965df9095b8bc7d3020ade2099bc62a05d..2036a9603bf99a236599613fcfacf2bcbff548c5 100644
--- a/src/main/resources/handlers.xml
+++ b/src/main/resources/handlers.xml
@@ -4,5 +4,8 @@
         <handler>
             <handler-class>com.kms.handler.LogHandler</handler-class>
         </handler>
+        <handler>
+            <handler-class>com.kms.handler.AuthHandler</handler-class>
+        </handler>
     </handler-chain>
 </handler-chains>
diff --git a/src/main/resources/history.ftl b/src/main/resources/history.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..8153e98b0d0d37fdbf27af333b7e789246fa40d9
--- /dev/null
+++ b/src/main/resources/history.ftl
@@ -0,0 +1,7 @@
+<#list payments as payment>
+
+    ${payment.paymentPaidTime}
+    <br>
+    ${payment.amount}
+    <br>
+</#list>
\ No newline at end of file