Extending Gophish for AI-Phishing Content Generation

Phishing simulation is one of the most effective ways to measure real-world security awareness, and Gophish has long been our tool of choice for managing campaign delivery, tracking, and reporting. As part of Halodoc's broader push to increase awareness by run phishing simulations every month rather than a few times a year, we needed our content creation process to keep pace. This blog walks through why we built an AI content generator directly into Gophish, how it's architected, and what we've seen after putting it to use.

Background

Running a monthly phishing simulation program (as opposed to occasional one-off campaigns) means the tooling around it needs to scale differently. Gophish itself is solid at what it's designed for: sending emails, tracking opens and clicks, and reporting results. What it doesn't help with is the part that happens before any of that, writing the actual pretext.

Before, this meant a security engineer opening Gophish's empty template editor and writing a phishing email and landing page from scratch, per campaign, per language. For quarterly campaign this was a manageable. Moving to a monthly cadence multiplied that cost directly, and made the weakest part of our process, manual content creation, the bottleneck for the whole program.

Problem Statement

Two distinct problems identified:

  • Content reuse erodes the simulation's validity.
    Once employees can recognize "the security team's phishing email" by a recycled subject line or layout, the simulation stops measuring real-world susceptibility, it starts measuring pattern memorization instead. Running monthly campaigns without a way to keep scenario genuinely varied would have made the results progressively less meaningful over time.
  • Writing a convincing pretext is a different skill than the one our team has.
    A pretext that a target will actually act on needs matching inline-CSS styling, a subject line that reads as urgent without being cartoonish, and phrasing consistent with how internal team would write it. Both Indonesian and English. That's a copywriting skill, not a security engineering one, and it doesn't scale by asking security engineers to get better at it.

Put together at a monthly occasion, across two languages, hand-writing pretexts that stay unpredictable was not sustainable with the team we had. We needed a way to generate that variety without spending security engineering time on writing copy.

Design Solution and Why This Approach

The most direct fix is to hire a copywriter, or task someone with a rotating content calendar. It would solve the writing-skill problem but not the cadence and volume problem. A person can still only produce so many genuinely distinct pretexts per month. What we actually needed was a way to produce dozens of plausible, on-brand pretexts on demand, in the format Gophish already expects, without a human writing each one from scratch.

That pointed to an AI content generator embedded directly in Gophish's existing workflow. Pick a scenario, a tone, and a target role, and the system produces both the phishing email and the landing page HTML, ready to plug into an existing campaign. A few design decisions shaped how we built it:

Multiple AI backends behind one interface.
Anthropic's API is a good default, but not every team's cost wants to route their spend tracking there instead of an existing AWS bill, while some scenarios call for keeping data from leaving the building entirely. Rather than lock into one provider, we defined a single Provider interface with four interchangeable implementations, Anthropic, OpenAI, Bedrock, and a local Ollama instance, selected by config. None of them import an official vendor SDK; each is a plain HTTP client, since an SDK is a lot of dependency weight for what's really one HTTP call with a specific header format, and Gophish itself has always kept a lean dependency tree.

Validation with retry, not validation with rejection.
Generated output has to satisfy hard requirements Gophish depends on, most importantly the {{.URL}} and {{.TrackingURL}} template variables, without which Gophish can't track opens or clicks at all. Rather than reject a failed generation and hand the error back to the user, the handler feeds the specific validation errors back into the next prompt as retry hints, for up to three attempts so a second attempt is told exactly what it got wrong instead of guessing again from scratch. If all three attempts still fail, the partial result and the specific errors are returned so the person can fix it by hand or retry with adjusted input.

Presets plus free text, not just free text.
The generator ships with 18 scenario presets (ie. password reset, invoice payment, and a couple of Indonesia-specific ones like a BPJS data update notice), each with an Indonesian and an English label, on top of the option to write a free-text scenario or layer extra context onto a preset. This keeps the common cases fast to generate while still allowing a fully custom pretext when a campaign calls for one.

Architecture

In practice, embedding the generator meant four additions to an unmodified Gophish install:

  1. A new package
    (ai/) this new package will implement the provider abstraction, prompt construction, and validation. It doesn't import or modify anything in Gophish's existing models or controllers packages, only calls into them.
  2. One new database table
    (ai_config) hold the provider choice and encrypted credentials.
  3. A handful of new API routes
    Namely (/api/ai/generate, /api/ai/config, /api/ai/config/validate, /api/ai/ollama/models) it all registered on Gophish's existing API router the same way other endpoint is.
  4. A Generate with AI button
    The button will opens a two-step modal
    1. Configuration form (scenario, tone, role, language)
    2. A preview with editable name, subject, and HTML field

More importantly, saving from the modal creates no new object types. Clicking "Save" uses Gophish's standard POST /api/templates/ or POST /api/pages/ endpoints with the generated name, subject, and HTML. Once saved, generated items act as standard templates or pages, using the existing campaign, send, and tracking pipelines. The generator merely supplies inputs for standard creation calls without modifying campaign building, scheduling, or delivery.

This keeps the integration low-risk, as models.Campaign, models.Template, and the send pipeline remain untouched. Removing the ai package and its UI buttons leaves Gophish functioning exactly as before.

The flow starts when someone picks a scenario, tone, and target role in the Gophish UI. That request is turned into a prompt carrying the safety guardrails and formatting requirements, brand logos, nested HTML tables, matching brand colors, and the mandatory {{.TrackingURL}} tracking pixel and sent to whichever provider is configured.

The response comes back as text, which is not the same as coming back clean. AI models are consistently inconsistent about JSON formatting: responses arrive wrapped in a markdown code fence, or with a literal newline character embedded inside a JSON string because the generated HTML spans multiple lines. A shared parser strips the fences and walks the string byte by byte to escape unescaped control characters inside quoted values, without touching anything outside a string, before the result is unmarshalled:

Once parsed, the output goes through validation. checking for the required Gophish template variables and basic HTML structure. A pass returns the content ready for use. A failure triggers the retry loop described above, feeding the specific errors back as hints for up to three attempts, before falling back to returning the partial result with its errors.

Structurally, the generator sits as its own package alongside Gophish's core send/tracking pipeline, rather than being woven into it. The Provider interface is the only thing Gophish's handler talks to directly; swapping Anthropic for Bedrock, or adding a fifth provider later, doesn't touch the handler, the parser, or the validator.

Guardrails Around the Generator Itself

A tool that can produce realistic, brand-matched phishing content on demand is worth being deliberate about who can use it, and how. A few things are enforced at the access-control layer rather than left to convention:

  • Generating content itself requires an authenticated, non-view-only account. Anyone with a view-only Gophish account is blocked from hitting the generate endpoint at all; it's restricted to accounts with permission to modify objects.
  • Provider credentials are encrypted at rest (AES-256-GCM) rather than stored as plaintext, and every change to the stored AI configuration records who made it and when.

Experiment and Results

Two examples from actual campaigns. The first uses an annual performance review scenario, styled to match Halodoc's own internal HR communications a "Hi Halosquad" header with the illustration style employees already recognize, addressed to the target by name, and signed by "HR Department, Halodoc." The urgency is understated rather than alarming: a 24-hour window to "complete your review." The giveaway is in the link itself, a domain that reads as our HR platform at a glance but is a typosquat of it, close enough to pass a quick read, wrong enough to catch on inspection.

The second impersonates a Google Workspace password-expiry notice, addressed to the target's actual work email, using Google's own visual language (logo, typography, button styling) rather than an approximation of it. The urgency here is more explicit, a 24-hour password expiry tied to "a scheduled security update" with a fallback link using an IP-based hostname via a dynamic-DNS wildcard service, the kind of raw-IP-behind-a-domain-name pattern that's a classic tell once you know to look for it, but easy to miss at a glance.

Both were generated end to end, copy, styling, and the embedded Gophish tracking variables, without a security engineer hand-writing either one. The realism came from the generator matching the visual and tonal conventions of genuine internal and vendor communications. The phishing indicators built into each (the typosquatted domain, the IP-based link) are exactly the kind of thing the monthly program is meant to train people to notice.

Where the effort actually moved.
This is the number that matters most to us operationally: standing up a full campaign, pretext, landing page, and both languages, used to block 1–2 weeks of a security engineer's time. With the generator, that's down to about 3 hours per campaign, most of which is review and QA rather than writing.

Activity

Before (manual)

After (AI-assisted)

Time to stand up one full campaign (pretext + landing page, both languages)

1–2 weeks

~3 hours

Number of genuinely distinct pretexts feasible per quarter

4–5, hand-written

In the hundreds, combining 18 scenarios × 2 languages × 2 tones × free-text customization

Skillset required to run a campaign

Security engineering + ad hoc copywriting + hand-writing HTML

Picking a scenario, tone, and target role, then reviewing the output — no HTML or copywriting skill needed

That last row is arguably the bigger unlock than the raw time saved. whoever runs the campaign no longer needs to be able to write HTML or convincing copy by hand, which means the program isn't bottlenecked on the one or two people who happen to have both skills.

Conclusion

The problem we set out to solve was narrow, a monthly phishing simulation program needs a steady supply of pretexts that don't repeat, and hand-writing them at that pace wasn't sustainable. Embedding an AI content generator directly into Gophish's existing workflow solved that without asking security engineers to become copywriters, and without asking the program to slow back down to a quarterly cadence to keep the content team's workload manageable.

As the monthly cadence continues, we'll keep refining the generator based on what each campaign teaches us, and we'll share harder production numbers once we have a full quarter behind us.


Join Us

Scalability, reliability and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels, and if solving complex problems with challenging requirements is your forte, please reach out to us with your resumé at careers.india@halodoc.com.

About Halodoc

Halodoc is the number one all-around healthcare application in Indonesia. Our mission is to simplify and deliver quality healthcare across Indonesia, from Sabang to Merauke.
Since 2016, Halodoc has been improving health literacy in Indonesia by providing user-friendly healthcare communication, education, and information (KIE). In parallel, our ecosystem has expanded to offer a range of services that facilitate convenient access to healthcare, starting with Homecare by Halodoc as a preventive care feature that allows users to conduct health tests privately and securely from the comfort of their homes; My Insurance, which will enable users to access the benefits of cashless outpatient services more seamlessly; Chat with Doctor, which allows users to consult with over 20,000 licensed physicians via chat, video or voice call; and Health Store features that allow users to purchase medicines, supplements and various health products from our network of over 4,900 trusted partner pharmacies. To deliver holistic health solutions in a fully digital way, Halodoc offers Digital Clinic services, including Haloskin, a trusted dermatology care platform guided by experienced dermatologists.
We are proud to be trusted by global and regional investors, including the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek, and many more. With over USD 100 million raised to date, including our recent Series D, our team is committed to building the best personalized healthcare solutions, and we remain steadfast in our journey to simplify healthcare for all Indonesians.