Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Rules
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ preprocess do
Nanoc::Filters::AlgoliaFilter.new(config: config).destroy_index
end

postprocess do
next if ENV['NANOC_ENV'] == 'development'

AlgoliaExternalIndexer.new(config: config).run
end

compile '*' do
if item.identifier == '/_common_links/'
nil
Expand Down
11 changes: 11 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ checks:
- https://www.npmjs.com/package/cloudmailin # cloudflare protection raises 403
- https://docs.cloudmailin.com/features/dmarc-verification/ # self-referential redirect

# Algolia search: pages from the main website fetched and indexed at
# build time alongside the docs content (see lib/algolia_external.rb).
# Application ID, API key and index name come from the ALGOLIA_* env vars.
algolia:
external_pages:
- https://www.cloudmailin.com/legal/security-and-privacy-policy
- https://www.cloudmailin.com/legal/terms-of-service
- https://www.cloudmailin.com/legal/acceptable-use-policy
- https://www.cloudmailin.com/legal/refund-policy
- https://www.cloudmailin.com/plans-and-pricing

# Deploy using fog
deploy:
default:
Expand Down
2 changes: 1 addition & 1 deletion content/_common_links.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
[CloudMailin]: https://www.cloudmailin.com
[contact us]: https://www.cloudmailin.com/contact_us
[signup]: https://www.cloudmailin.com/plans-and-pricing
[outbound account]: https://www.cloudmailin.com/outbound/senders
[outbound account]: https://www.cloudmailin.com/outbound/sources
[WebhookApp]: https://www.webhookapp.com
[Heroku]: https://www.heroku.com
45 changes: 45 additions & 0 deletions content/getting_started/cancelling_your_account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Cancelling your Account or Subscription
description:
How to cancel your CloudMailin subscription, delete your addresses or
remove your user account entirely.
---

# Cancelling your Account

You can cancel your CloudMailin subscription at any time from your
[user page](https://www.cloudmailin.com/user). There are two options,
depending on how much you want to remove:

## Delete all addresses and subscriptions

The **Delete All Addresses** button on your
[user page](https://www.cloudmailin.com/user) deletes all of your addresses
and cancels any paid subscriptions. You will no longer be billed, but you
can continue to log in and create new addresses later if you need to.

## Delete your user entirely

The **Delete User** button removes your user, your account and all
associated resources. You will not be able to log in again afterwards.

> Both actions take effect immediately and **cannot be undone**. Neither
> deletes resources in an account owned by someone else, such as a team
> account you are a member of.

## Accounts created through a provider

If you signed up through a provider such as the Heroku add-on, that
provider handles your billing, so the subscription must be cancelled
there — remove the add-on from your application and the CloudMailin
account will be cancelled automatically.

## Downgrading instead

If you want to reduce your bill rather than leave, you can switch to a
smaller plan — including the free plan — from the
[plans page](https://www.cloudmailin.com/plans-and-pricing). See the
[refund policy](https://www.cloudmailin.com/legal/refund-policy) for how
refunds are handled, or
[contact us](https://www.cloudmailin.com/contact_us) if you have any
questions.
2 changes: 1 addition & 1 deletion content/other/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ description: The CloudMailin security and privacy policy can be found on the

For questions or concerns relating to security please see the security section
of our main site:
[https://www.cloudmailin.com/privacy-and-security](https://www.cloudmailin.com/privacy-and-security)
[https://www.cloudmailin.com/legal/security-and-privacy-policy](https://www.cloudmailin.com/legal/security-and-privacy-policy)
You can also [contact us] at any time for more information.
2 changes: 1 addition & 1 deletion content/outbound/sending_email_via_json_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,5 @@ emails with your own library or migrating from another provider.
<%= render_api_fields("components/schemas/RawMessage", include_readonly: false, except: %w[headers attachments]) %>

[Client Library]: #client-libraries
[SMTP Accounts]: https://www.cloudmailin.com/outbound/senders
[SMTP Accounts]: https://www.cloudmailin.com/outbound/sources
[SMTP]: <%= url_to_item('/outbound/sending_email_with_smtp/') %>
2 changes: 1 addition & 1 deletion content/outbound/sending_email_with_smtp.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ email over SMTP:
* <%= link_to_item(item) %><% end %>


[account page]: https://www.cloudmailin.com/outbound/senders
[account page]: https://www.cloudmailin.com/outbound/sources
83 changes: 83 additions & 0 deletions lib/algolia_external.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
require 'algolia'
require 'net/http'
require 'nokogiri'

# Fetches a fixed list of pages from the main website (algolia:
# external_pages in config.yaml) and indexes them alongside the docs
# content, so searches for things that live on www.cloudmailin.com
# (legal policies, pricing) return results in the docs search.
class AlgoliaExternalIndexer
# Existing docs records reach ~39KB, so the record-size limit on this
# plan comfortably allows full page text (the longest external page is
# currently ~11K chars — truncating it would drop the GDPR/DPA text
# people actually search for).
MAX_CONTENT_LENGTH = 20_000
MAX_REDIRECTS = 3

def initialize(config:)
@pages = config.dig(:algolia, :external_pages) || []

index = config.dig(:algolia, :index) || ENV['ALGOLIA_INDEX']
@skip_index = index.nil? || index.empty?
return if @skip_index

app_id = config.dig(:algolia, :application_id) || ENV['ALGOLIA_APPLICATION_ID']
api_key = config.dig(:algolia, :api_key) || ENV['ALGOLIA_API_KEY']

raise ArgumentError, 'Missing algolia:application_id' unless app_id
raise ArgumentError, 'Missing algolia:api_key' unless api_key

client = Algolia::Search::Client.create(app_id, api_key)
@index = client.init_index(index + "_#{ENV['NANOC_ENV']}")
end

def run
return if @skip_index

@pages.each do |url|
record = fetch(url)
if record.nil?
warn "Failed to fetch #{url} - not indexed"
next
end

@index.save_object(
record.merge(objectID: url, external: true, deprecated: false, no_index: false)
).wait
puts "Indexed #{url} in Algolia"
end
end

private

def fetch(url, redirects_left = MAX_REDIRECTS)
response = Net::HTTP.get_response(URI(url))

case response
when Net::HTTPRedirection
return nil if redirects_left.zero?

fetch(URI.join(url, response['location']).to_s, redirects_left - 1)
when Net::HTTPSuccess
extract(response.body)
end
rescue StandardError => e
warn "#{url}: #{e.class}: #{e.message}"
nil
end

def extract(html)
doc = Nokogiri::HTML(html)
doc.css('script, style, nav, header, footer, noscript').each(&:remove)

content = (doc.at_css('main') || doc.at_css('body'))
.text.gsub(/\s+/, ' ').strip[0, MAX_CONTENT_LENGTH]
description = doc.at_css('meta[name="description"]')&.[]('content')

{
title: doc.at_css('title')&.text.to_s.sub(/\s*[|·—-]\s*CloudMailin.*\z/i, '').strip,
description: description || content[0, 160],
content: content
}
end
end
Loading