Facebook Chatbot

Automated customer engagement via Messenger

ChatbotAutomationJava
2023ChatbotAutomationJavaRepo ↗

System Architecture

system-first

A rule-driven messaging assistant built for internal operations. Incoming messages are classified, matched against policies, and routed through clearly separated layers: desktop UI/control, automation logic, and persistence/export infrastructure.

Messaging Channel (Facebook) ↓ Bot Orchestrator (Policy Engine) ↓ Policy Data (keywords/templates) ↓ Storage (SQLite/CSV/Excel) ↓ Escalation / Handoff (Email / Manual follow-up)

Domain Rules

principles

The system is designed for consistent behavior under real-world variability. Message classification is deterministic, policy lookup is centralized, and when confidence is low the system transitions to explicit fallback paths (question detection or escalation) instead of guessing silently.

Impact: This reduced repetitive manual replies and enabled faster, more consistent customer responses. Policies (keywords/templates) are data-driven, allowing behavior to evolve without rewriting core orchestration logic.

Trade-offs: A centralized policy engine is simple to operate for a small internal tool, but complexity can grow as policies expand. Future improvements could introduce strategy-based handlers and stronger typed intent models to better separate concerns and improve scalability.

Policy-first workflow

Messages move through a predictable pipeline: classify → match policy → respond → log → escalate when required.

Explicit fallbacks

When a confident match is not found, the system avoids silent guesses and instead transitions to explicit fallback paths such as question detection or human escalation.

Storage boundaries

Persistence and exports (SQLite/CSV/Excel) are isolated behind helper layers so policy logic remains readable and safe to change.

Operational focus

Designed for real operational use: predictable rules, log-based observability, and safe human handoff when necessary.

Example: The same boundary discipline used in PocketQuest is applied here. Orchestration remains concise while infrastructure details (Selenium/SMTP/storage) stay isolated behind helper layers.

Selected Code

excerpts

The following excerpts illustrate how the bot orchestrates real-world messaging: deterministic branching, centralized policy lookup, reliable infrastructure boundaries, and safe human handoff paths.

Orchestrator branching (core policy flow)

The central workflow: extract structured details, branch deterministically, log outcomes, and escalate to a human when needed.

// src/models/Automate.java (excerpt)
HashMap<String, String> details = Util.fetchDetails(customerMsg);
String email = details.get("email");
String phone = Util.formatPhoneNumber(details.get("number"));

if(!email.contentEquals("")) {
  String reply = BotAnswers.getInstance()
    .getAnswerHasEmail()
    .replace(AnswerReferences.AGENT_NAME.toString(), agentName)
    .replace(AnswerReferences.AGENT_NUMBER.toString(), agentNumber);

  sendMessage(reply);
  ExcelHelper.saveMessage(name, customerMsg);

  Email.send(record.getString("subject"),
              record.getString("email"),
              mailText);
  return;
}

// Keyword-based response
String msg = DBHelper.getKeywordBasedMessage(itemId, customerMsg);
if(msg != null && !totalMessage.contains(msg)) {
  sendMessage(msg);
  return;
}

// Question fallback → escalate
if(customerMsg.contains("?")) {
  Email.send("Need Manual Reply", agentEmail, customerMsg);
}

Policy lookup engine (AND/OR/single/global)

Rules are evaluated in a clear priority order so behavior stays predictable as policies evolve.

// src/storages/DBHelper.java (excerpt)
public static String getKeywordBasedMessage(String itemId, String keyword)
    throws SQLException {

  String response;

  response = getAndFromTagBased(itemId, keyword);
  if(!response.isEmpty()) return response;

  response = getOrFromTagBased(itemId, keyword);
  if(!response.isEmpty()) return response;

  response = getOnlyOneTagBased(itemId, keyword);
  if(!response.isEmpty()) return response;

  return getFromAllTagBased(keyword);
}

SQLite PreparedStatement (policy stays in data)

Keyword policies live in SQLite so operations can update behavior without changing orchestration code.

// src/storages/DBHelper.java (excerpt)
PreparedStatement ps = DB.getInstance().prepareStatement(
  "SELECT * FROM keyword " +
  "WHERE ? LIKE '%'||keyword.keyword||'%' " +
  "AND product_id = ?"
);

Infrastructure boundary: ChromeDriver lifecycle

WebDriver creation/configuration is centralized to keep orchestration readable and resilient.

// src/models/apis/ChromeDriverHelper.java (excerpt)
if(conn == null) {
  System.setProperty(WEB_DRIVER_ID, WEB_DRIVER_PATH);

  ChromeOptions options = new ChromeOptions();
  options.addArguments("--disable-extensions");
  options.addArguments("--ignore-certificate-errors");

  conn = new ChromeDriver(options);
}
return conn;

Meaningful failure semantics (wrapper + custom exception)

Selenium failures are converted into domain-level exceptions so failures are explicit and diagnosable.

// src/models/utils/Util.java (excerpt)
public static WebElement findElement(FindElementBy by, String target)
    throws ElementNotFoundException {
  try {
    WebDriver driver = ChromeDriverHelper.getInstance().getConnection();
    return driver.findElement(By.xpath(target));
  } catch (Exception e) {
    throw new ElementNotFoundException(
      Util.getStackTrace(Thread.currentThread().getStackTrace())
    );
  }
}

These excerpts reflect a system-first design: a single orchestrator with deterministic branching, a data-driven policy layer (SQLite), isolated infrastructure helpers (Selenium/SMTP), and explicit escalation paths for low-confidence scenarios.

Although this project is a Java desktop tool, the same architectural discipline carries into my later full-stack work: explicit boundaries, centralized policies, and predictable behavior that is easy to operate and maintain.