Tue, 21 Dec 2004

Yahoo Money Manager No More

Yahoo is shutting down their Money Manager.

We regret to notify you that we are closing Yahoo! Money Manager, effective February 1, 2005, and your account will be automatically cancelled at this time. This closure means that we will no longer offer the following tools: Expense Manager, Net Worth, Finance Planner, and Account Access for banking, credit card, investment, and loan accounts.

It sounds like they're basically gutting Yahoo Finance if they're removing the ability to access investment accounts. I guess most banks offer similar services now, but Yahoo has always been good at keep a nice, simple interface.

I wonder if Google is working on something in this area.

business | Permanent Link

Blockbuster Could Use Better Developers

How many stars does Napoleon Dynamite get?

Hmm, the Enhanced Widescreen Letterbox DVD version must be much better.

Update (Fri, 24 Dec 2004): I received Napoleon Dynamite from Blockbuster. The sleeve that DVD came said that it was disc "1 of null".

tech | Permanent Link

Thu, 09 Dec 2004

Ending Social Security

Hans F. Sennholz has an interesting proposal for ending Social Security. The basic ideas are:

The holiday season is a good time to think about the 15.3% of your income that the government has taken from you to gift to others.

culture | Permanent Link

Time Magazine Ads

There was one ad in a recent edition of Time Magazine that caught my eye. We've been trying to figure out how to best market Postica since officially going into production in July. I'm pretty skeptical about the effectiveness of advertising in general. Since we're bombarded with so much of it, I think most people simply ignore it. But every once in a while, some advertisement will catch my eye. I don't know if the fact that I read it means that it actually increases sales of the product advertised, but it obviously can't be worse than an ad that nobody reads.

The full-page ad in Time was for men's white dress shirts. Just plain, white button-down shirts that I buy every 5 years from Ross when the one I bought five years previously no longer fits. I had never heard of the brand; it was a man's name (two first names, I think). It was one particular shirt, rather than an ad for the brand in general.

What made me read the ad was simply that I wouldn't expect to see such an ad in Time Magazine. I wonder how sales of that shirt are doing. I believe one could order the shirt in question online or by calling an 800 number, similar to ads for overpriced cds composed of the Best Love Songs of Some Period within Some Genre or "collectible" coins that aren't legal tender.

Does placing an ad where potential customers wouldn't expect to see it help sales?

business » marketing » advertising | Permanent Link

Mon, 22 Nov 2004

Selling Apple

I sold half my Apple stock at $61.33 today. It's pretty damn expensive at this level.

business | Permanent Link

Thu, 18 Nov 2004

Google, Say It Isn't So Already

Some web hosting customers, especially those with dubious business plans who have hired SEO witch doctors, request that every web site be placed on a separate IP address. The theory is that Google is likely to discount the links between sites on the same IP address because such sites may be trying to artificially increase their PageRank. It would be silly for Google to do this since there could be hundreds or thousands of unrelated sites that share an IP address, simply because they are customers of the same web hosting company. Nonetheless, this seems to be a widely-held belief.

This is frustrating to those in the hosting business because IP(v4) addresses are a finite resource, and here in North America, requests for new IP addresses for IP-based web hosting must include a technical justification.

Today, Sean emailed Google to ask for clarification. I hope that Google will publicly state that using multiple IP addresses cannot be used to inflate PageRank.

Update (Thu, 09 Dec 2004): Google responds by saying, go ask someone else to speculate.

Date: Thu, 09 Dec 2004 14:33:35 -0800
From: help@google.com
Subject: Re: [#17061378] Regarding the need for a static IP Address

Hi Sean,

Thank you for writing to us. Please note that we don't comment on
webmaster techniques or the details of our search technology beyond what
appears on our site.

We've dedicated an entire section of our site to answering the most common
questions from those who maintain and/or promote websites. You'll find all
of our publicly available information posted at
http://www.google.com/webmasters/index.html

Besides this section of our site, we've created a newsgroup discussion
forum for passionate Google users. At http://groups.google.com in the
http://groups.google.com/groups?q=google.public.support.general group,
many webmasters and Google users share their questions and expertise.

We recommend performing an advanced search on this group if you feel your
question is particularly challenging and you've been unable to find an
answer on our site. To do so, go to
http://www.google.com/advanced_group_search?hl=en and enter your search
terms in one of the 'Find messages' fields at the top of the page. Type
'google.public.support.general' in the 'Newsgroup' search field, and click
'Google Search.' If you don't find an answer to your question, you can
always post your question to the group to see if other newsgroup users
have helpful advice. Please be aware that this content isn't posted by
Google, and we cannot verify its accuracy.

Regards,
The Google Team

tech | Permanent Link

Apache Virtual Host Bandwidth Monitoring

Also, a good example of the READ-COMMITTED isolation level

mod_accounting is a simple Apache module for recording bandwidth usage on a per-virtual host basis. It can write to either a MySQL or PostgreSQL database. You just add a couple directives to httpd.conf to tell mod_accounting which database to write to, the query to run, and how often to write to the database.
Here's the configuration I use.

<IfModule mod_accounting.c>
AccountingQueryFmt "   \
  INSERT INTO          \
    vhost_accounting   \
  (                    \
    bytes_in,          \
    bytes_out,         \
    virtual_host,      \
    host               \
  )                    \
  values               \
  (                    \
    %r,                \
    %s,                \
    LOWER('%h'),       \
    'host.example.com' \
  );"
AccountingTimedUpdates 300
AccountingDatabase vhost_accounting
AccountingDatabaseDriver mysql
AccountingDBHost stats.example.com 3306
AccountingLoginInfo vhost_accounting XXXXXXXX
</IfModule>

And the corresponding table.

CREATE TABLE `vhost_accounting` (
  `virtual_host` varchar(128) NOT NULL default '',
  `bytes_in` int(20) unsigned NOT NULL default '0',
  `bytes_out` int(20) unsigned NOT NULL default '0',
  `timestamp` timestamp(14) NOT NULL,
  `host` varchar(128) NOT NULL default '',
  `id` bigint(20) unsigned NOT NULL auto_increment,
  PRIMARY KEY  (`id`)
) TYPE=InnoDB

If you have tables of hosts and virtual hosts, you'll probably want to normalize the database and modify AccountingQueryFmt to use the foreign key to the vhost. For example,
AccountingQueryFmt "INSERT INTO vhost_accounting (bytes_in, bytes_out, virtual_host_id) SELECT %r, %s, id FROM vhost WHERE virtual_host_name = LOWER('%h');"

There are a couple issues that make deploying mod_accounting to multiple machines a bit tricky. Each apache process does its own logging, and thus, maintains its own connection to the database server. If you have hundreds of Apache processes and multiple servers, you'll quickly have thousands of open connections to the database. To reduce the number of concurrent connections, I made a one-line change to mod_accounting to close the connection to MySQL after writing to the database so a new connection is used each time data is written to the database.

The next problem is simply the large amount of data that is collected. It quickly grows to the point where generating reports in real-time is too slow. To solve this problem, I created a summary table, vhost_accounting_summary, to consolidate the data produced by each Apache process. I also decided that hourly statistics were sufficient for my needs.

The records in the vhost_accounting table are removed once the data has been inserted into vhost_accounting_summary. One other table, vhost_accounting_timestamp, is used to keep track of when the summary table was last updated and to ensure that data from vhost_accounting does not accidentally get written to vhost_accounting_summary multiple times. (More details below.)

CREATE TABLE `vhost_accounting_summary` (
  `virtual_host` varchar(128) NOT NULL default '',
  `bytes_in` int(20) unsigned NOT NULL default '0',
  `bytes_out` int(20) unsigned NOT NULL default '0',
  `timestamp` timestamp(14) NOT NULL,
  `host` varchar(128) NOT NULL default '',
  KEY `virtual_host` (`virtual_host`),
  KEY `idx_timestamp_host` (`timestamp`,`host`)
) TYPE=InnoDB

CREATE TABLE `vhost_accounting_timestamp` (
  `timestamp` timestamp(14) NOT NULL
) TYPE=InnoDB

And here's the SQL to update the vhost_accounting_summary table.

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT timestamp FROM vhost_accounting_timestamp FOR UPDATE;
SELECT @max_id:= max(id) FROM vhost_accounting;
INSERT INTO
        vhost_accounting_summary
SELECT
        virtual_host,
        SUM(bytes_in),
        SUM(bytes_out),
        DATE_FORMAT(timestamp, '%Y-%m-%d %H:00:00') AS dayhour,
        host
FROM
        vhost_accounting
WHERE
        id < @max_id
GROUP BY
        virtual_host,
        dayhour;
DELETE FROM vhost_accounting WHERE id < @max_id;
UPDATE vhost_accounting_timestamp SET timestamp = now();
COMMIT;

There are a couple important points to make about the SQL above. We want to make sure that mod_accounting never has to wait to write to the vhost_accounting because of a lock. In addition, we want to ensure that, if multiple copies of the update script get run concurrently, it does not cause the same data to be written to vhost_accounting_summary multiple times.

Mod_accounting does its work while processing a request. If mod_accounting is waiting for a lock, the Apache process will not finish processing the current request until the lock is removed or a lock timeout occurs. This can quickly cause all of the Apache children to be tied up and prevent new requests from being processed.

To prevent a lock from being set, we only act on records with a primary key one less than the maximum. This prevents a next-key lock from being set when we delete the records we have just processed.

To prevent multiple instances of the script from causing redundant data to be stored, we do a locking read of the timestamp by using FOR UPDATE. This lock only affects this summary script since mod_accounting does not use the vhost_accounting_timestamp table. Furthermore, we must use the READ-COMMITTED isolation level to guarantee that once the script receives the lock, it doesn't read any rows from vhost_accounting that have already been deleted.

And finally, we're ready to generate some reports…

SELECT
  SUM(bytes_in) AS bytes_in,
  SUM(bytes_out) AS bytes_out,
  SUM(bytes_in + bytes_out) AS total,
  virtual_host,
  host
FROM
  vhost_accounting_summary
WHERE
  timestamp >= '2004-11-18 00:00:00' AND
  timestamp <= '2004-11-18 23:59:59'
GROUP BY
  virtual_host
ORDER BY
   total DESC

tech | Permanent Link

Tue, 16 Nov 2004

Exchange Replacements

I spent a few hours today researching Exchange replacements. These are products that are designed to replace Microsoft Exchange on the server, but still allow use of Outlook as a client, including the much-beloved calendaring features.
Here's what I came up with.

Date: Tue, 16 Nov 2004 16:24:55 -0800
From: "Christian G. Warden" <cwarden@zerolag.com>
Subject: Exchange replacement analysis - phase 1

There are a handful of products that claim to be Exchange replacements.
They all work in the same manner, using a custom MAPI connector, which
is basically a plug-in for Outlook, to access the server.  Each version
of Outlook has different features so most of these products only work
with certain versions of Outlook.  Because I'm not very familiar with
Outlook, it is difficult for me to tell if these products fully support
the features of Outlook.  We'll need to setup a test environment to
fully evaluate any of these products.

OpenGroupware[1]
This was previously a closed source server that was open sourced a
couple years ago.  I evaluated it briefly a year or so ago, and it
seemed stable and featureful, but had a bit of a clunky web interface.
It looks like development is pretty active, though.  I haven't evaluated
the Outlook connector, ZideLook[2], which is a commercial product which
costs about $50 per client.  There is no demo of ZideLook available.
ZideLook communicates with OpenGroupware using WebDAV.
OpenGroupware just handles the groupware functionality and integrates
with third-party IMAP servers.

1. http://www.opengroupware.org/en/index.html
2. http://esd.element5.com/product.html?cart=1&productid=517934&languageid=1&nolselection=1¤cies=EUR

SUSE LINUX Openexchange Server[3]
This is a commercial product.  It is distributed a full linux
distribution and cannot be installed on an existing Linux system. (Such
an installation would not be supported at least.)  Pricing is unclear.
The product is supposed to be available for purchase online at
novell.com, but isn't, perhaps because they are currently integrating
the product with Novell's Groupwise.
There is an online demo[4] and the Outlook connector is available for
download[5].  Openexchange is made up of a number of open source
components and comFire, the groupware component, which was licensed from
a company called Netline.  comFire has recently been open sourced by
Netline as Open-Xchange[6], but the Outlook connector is not licensed for
use with Open-Xchange.  The Outlook connector communicates with the
server using WebDAV.  There is a good article about Openexchange[7].

3. http://www.suse.com/us/business/products/openexchange/index.html
4. http://www.suse.com/us/business/products/openexchange/demo.html
5. http://www.suse.com/us/business/products/openexchange/download.html
6. http://mirror.open-xchange.org/ox/EN/product/
7. http://www.linux-magazine.com/issue/48/Suse_Linux_Openexchange_41.pdf

Bynari Insight Server[8] and Insight Connector[9]
I believe Bynari was the first company with an "Exchange replacement on
Linux" product.  Their Outlook connector allows calendars and address
books on an IMAP server.  It claims to require the Insight Server,
though Insight Server uses Cyrus as the IMAP server, so it may work with
a normal Cyrus server.  Insight Server is composed of a number of open
source products such as Postfix, OpenLDAP, and Apache.  Bynari seems to
think most of the value is in the Connector since a 1000 user license
for Insight Connector is $17,000, and a 1000 user license for a bundled
Insight Server and Insight Connector is $18,000.  (Insight Server
without the Connector is also sold for $1,000.)  A demo is available.

8. http://www.bynari.net/index.php?id=1169
9. http://www.bynari.net/index.php?id=7

BILL Workgroup Server[10]/Exchange4Linux[11]
Documentation is kind of spotty on this one.  I don't think it's worth
evaluating except as a last resort.

10. http://www.billworkgroup.org/billworkgroup/home
11. http://www.exchange4linux.com/exchange4linux/Home

None of the Above (IMAP/LDAP/SMTP/WebDAV or FTP)
Depending on the customer's needs, perhaps Outlook in "Internet Mail
Mode" will be sufficient.  IMAP supports shared folders, but I don't
know if it supports setting ACLs.  Outlook also supports LDAP for
address books, but I don't know if supports updating the directory.
Outlook can send meeting requests and responses over email and publish
free/busy time over FTP (and, I think, either WebDAV or HTTP PUT), but I
don't know if this would meet the customer's needs.


I recommend trying out Openexchange first as it seems to be the most
open and widely deployed.

Christian

Comments from anyone who has deployed one of these products for use with Outlook would be appreciated.

tech » mail | Permanent Link

Promises are Useful

Henrik Joretag tweeted that he doesn't like promises in javascript.

I’m just gonna say it: I really don’t like promises in JS. It’s a clever pattern, but I just don’t see the upside, sorry.

— Henrik Joreteg (@HenrikJoreteg) October 26, 2014

I've been working on a large javascript application for the past two years, and I've found promises to be invaluable in expressing the dependencies between functions.

But Henrik's a smart guy, having authored Human Javascript and written lots of Ampersand.js, so I decided to look at a recent case where I found the use of promises helpful, and try to come up with the best alternative solution.

The Backstory

I recently discovered a bug in our app initialization. It's an offline-first mobile app. We have a function that looks like this:

App.init = function() {
   return Session.initialize()
      .then(App.initializeModelsAndControllers)
      .then(App.manageAppCache)
      .then(App.showHomeScreen)
      .then(App.scheduleSync)
      .then(_.noop, handleError);
};

When the app starts up, it needs to:

  1. Load some user info from the local datastore, if possible; otherwise, from the network
  2. Load metadata about the models used in the app and initialize controllers
  3. Set up polling for application cache updates and event handles to appcache events, to prompt the user to restart when a new version of the app is available, for example
  4. Show the initial application screen
  5. Schedule a sync of metadata and data
  6. and if an error occurs during any of these steps, handle it gracefully

The chaining of promise-returning functions in App.init nicely expresses the dependencies between these steps.
We need valid session info for the user before we can initialize the models, which may require fetching data over the network.
We can't show the initial home screen until the controllers have been initialized to bind to DOM events and display information from the models.
If there's a new version of the app available, we want to prompt the user to restart before they start using the app, and we don't want to schedule a sync until the app is being used.

Unfortunately, during a recent upgrade some users ran into a bug in some code I wrote to upgrade the local database, called from App.initializeModelsAndControllers. The result was an uncaught javascript error, Uncaught RangeError: Maximum call stack size exceeded, on the initialization screen which is displayed before App.init is called.

I fixed the bug which broke upgrading some databases, but I also realized that the dependency between App.initializeModelsAndControllers and App.manageAppCache should be reversed. If there's an upgrade available, we want users to be able to restart into it before initializing the app further (and running more code with a larger surface area for possible bugs).

The diff to make this change with our promise-returning functions is nice and simple:

--- a/app.js
+++ b/app.js
@@ -1,7 +1,7 @@
 App.init = function() {
   return Session.initialize()
-     .then(App.initializeModelsAndControllers)
      .then(App.manageAppCache)
+     .then(App.initializeModelsAndControllers)
      .then(App.showHomeScreen)
      .then(App.scheduleSync)
      .then(_.identity, handleError);

(The careful reader will notice that stuck users need a way of upgrading the broken app stored in the app cache. The cache manifest gets fetched automatically to check for and trigger an app update each time the page is loaded, so even though we haven't set up an event handler to prompt the user to restart after the update has been downloaded, if the user waits a few minutes to ensure that the app update is complete, then kills and restarts the app, the new version will be loaded.
The careful reader who happens to be a coworker or affected customer will note that this didn't actually work because our cache manifest requires a valid session and our app starts up without refreshing the OAuth session to speed up start-up time. An upcoming release of GreatVines app to the iOS app store will contain a new setting to trigger the session refresh at app initialization. Affected customers have been given a work-around that requires tethering the device to a computer via USB. Developers using the Salesforce Mobile SDK are welcome to email me for more info.)

Recapping the Question

App.init = function() {
   return Session.initialize()
      .then(App.initializeModelsAndControllers)
      .then(App.manageAppCache)
      .then(App.showHomeScreen)
      .then(App.scheduleSync)
      .then(_.noop, handleError);
};

Now, back to the issue at hand. What would this function look like using an alternative to promises. Ignoring the possible use of a preprocessor that changes the semantics of javascript, the two likely candidates are callback passing and event emitter models.

The typical callback hell approach looks something like this:

App.init = function(success, error) {
   Session.initialize(function() {
      App.manageAppCache(function() {
         App.initializeModelsAndControllers(function() {
            App.showHomeScreen(function() {
               App.scheduleSync(success, error);
            }, error);
         }, error);
      }, error);
   }, error);
};

For simplicity, I've ignored the fact that the original version both had some error-handling and returns the error to the caller through a rejected promise, but I'm pretty sure this is not what Henrik had in mind. So what might an improved callback-passing approach look like?

App.init = function(success, error) {
   var scheduleSync = App.scheduleSync.bind(App, success, error);
   var showHomeScreen = App.showHomeScreen.bind(App, scheduleSync, error);
   var initializeModelsAndControllers = App.initializeModelsAndControllers.bind(App, showHomeScreen, error);
   var manageAppCache = App.manageAppCache.bind(App, initializeModelsAndControllers, error);
   Session.initialize(manageAppCache, error);
};

By pre-binding the callbacks, we avoid the nested indentations, but we've reversed the order of the dependencies, so that's not a great improvement.

I've failed to come up with a good callback-passing alternative to promises, so let's look at what event emitter approach would look like.

App.init = function() {
   BackboneEvents.mixin(App);
   App.once('sessionInitialized', App.manageAppCache);
   App.once('appCacheManaged', App.initializeModelsAndControllers);
   App.once('modelsAndControllersInitialized', App.showHomeScreen);
   App.once('homeScreenShown', App.scheduleSync);
   var initialized = App.trigger.bind(App, 'initialized');
   App.once('syncScheduled', initialied);
   App.once('sessionInitializationError appCacheManagementError modelsAndControllersInitializationError homeScreenError syncSchedulingError', function(error) {
      App.off('sessionInitialized', App.manageAppCache);
      App.off('appCacheManaged', App.initializeModelsAndControllers);
      App.off('modelsAndControllersInitialized', App.showHomeScreen);
      App.off('homeScreenShown', App.scheduleSync);
      App.off('syncScheduled', initialied);
      App.trigger('initializationError', error);
   });
   Session.initialize();
};

This approach does allow us to list dependencies from top to bottom, and doesn't take us into calllback hell, but it has some problems as well.
With the promise-returning and callback-passing approaches, each function was loosly coupled to the caller, returning a promise or accepting success and error callbacks. With this event emitter approach, we need to make sure both the caller and called agree upon the names of events. Admittedly, most functions are going to be more tightly coupled than these, regardless of the approach, having to agree upon parameters to the called function and parameters to the resolved promise or callback, but they don't need to share any special information like the event name to determine when the called function is done.

This event emitter example also requires more work to clean up when an error occurs.

Perhaps instead of using a single event bus, we could listen for events on multiple objects and standardize on event names. Events could be triggered on each function.

App.init = function() {
   var initialized = App.init.trigger.bind(App.init, 'initialized');
   var error = function(error) {
      App.init.trigger('error', error);
      Session.initialize.off('done', App.manageAppCache);
      Session.initialize.off('error', error);
      App.manageAppCache.off('done', App.initializeModelsAndControllers);
      App.manageAppCache.off('error', error);
      App.initializeModelsAndControllers.off('done', App.showHomeScreen);
      App.initializeModelsAndControllers.off('error', error);
      App.homeScreenShown.off('done', App.scheduleSync);
      App.homeScreenShown.off('error', error);
      App.scheduleSync.off('done', initialized);
      App.scheduleSync.off('error', error);
   };
   // Assume every function mixes in BackboneEvents
   Session.initialize.once('done', App.manageAppCache);
   Session.initialize.once('error', error);

   App.manageAppCache.once('done', App.initializeModelsAndControllers);
   App.manageAppCache.once('error', error);

   App.initializeModelsAndControllers.once('done', App.showHomeScreen);
   App.initializeModelsAndControllers.once('error', error);

   App.homeScreenShown.once('done', App.scheduleSync);
   App.homeScreenShown.once('error', error);

   App.scheduleSync.once('done', initialized);
   App.scheduleSync.once('error', error);

   Session.initialize();
};

This is very verbose, containing a lot of boilerplate. We could create a function to create these bindings, e.g.

var when = function(fn, next, error) {
   BackboneEvents.mixin(fn);
   fn.once('done', next);
   fn.once('error', error);
};
when(Session.initialize, App.manageAppCache, error);
...

But it doesn't feel like we're on the right path. We'd still have to handle unbinding all of the error handlers when an error occurs in one of the functions.

It's also worth pointing out that using an event emitter isn't really appropriate in a case like this where you want to get an async response to a specific function call. If the called function can be called multiple times, you don't know that the event that was triggered was in response to your call without passing additional information back with the event, and then filtering.

There's Got To Be A Better Way

Unsatisfied with any of the alternatives to promises I came up with, I replied to Henrik's tweet, and he said that he uses Async.js and Node's error-first convention. So let's look at what using async.series would look like.

App.init = function(callback) {
   async.series([
      Session.initialize,
      App.manageAppCache,
      App.initializeModelsAndControllers,
      App.showHomeScreen,
      App.scheduleSync,
   ], callback);

Finally, we've come to an alternative solution that feels equivalent to the promise one. The async.series solution has at least one benefit over the promise-chaining one: the Async library has a separate function, async.waterfall, to use when the return value from one function should be passed as a parameter to the next function in the series. In my original solution, it's not clear without looking at the function definitions whether the return value from each function is used by the next function.

Given the simple error handling in App.init, in which an error anywhere in the chain aborts the flow, and a single error handler function is responsible for handling the error from any of the functions, Async.js would be a decent replacement.

But for a more complicated flow, a promise-based approach would probably come out on top. For example, here's what App.initializeModelsAndControllers looks like:

App.initializeModelsAndControllers = function() {
   return Base.init()
      .then(Base.sufficientlyLoadedLocally)
      .then(App.handleUpgrades, App.remoteInit)
      .then(App.loadControllers);
};

This function introduces a fork in the chain of serialized functions:

  1. Initialize the models
  2. Check whether the app is ready to start up offline
  3. Initialize the controllers

It looks like this could be done with async.auto, but it would probably start getting messy.

tech | Permanent Link

Sat, 13 Nov 2004

Google Ads are Hard

We haven't had much luck with Google Ads for Postica so far. There's a lot of competition in the spam filtering market so it's hard to stand out among 10 ads for spam/virus filtering products and services on the same page.

It's frustrating having what I think is a great service, and not being able to reach potential customers. We're mostly focusing on selling through partners now, but we haven't completely given up on Google Ads yet. Here's our latest.

business » marketing » advertising | Permanent Link

Updated RSS Feeds

I've updated my screen-scraped rss feed of Northern Trust's Economic Research to include their International Comments.

Also, I don't think I mentioned it when I first set it up, but I also have a feed of the Prudent Bear news.

business | Permanent Link

From Techie to Business Owner

I've figured out why I find sales so unpleasant. Being a business owner requires a much different mind-set than that of a typical techie.

I've been working as a sys admin and web developer for the past seven years. As such, I only have to sell myself to an employer or client once, and typically, over a single one or two hour interview period. Having to sell a product or service to a customer who hasn't already "hired" you is a much different proposition.

Failure

As a techie, I don't often have to deal with failure. When working on a new project or fixing an existing system that is broken, I just keep working on it until it's done. Rarely must I concede defeat and give up hope of finding a solution.

Failure is a normal part of sales, though. Salespeople cannot always just keep working on a sales prospect until a deal is closed. This would likely result in restraining orders.

With Postica, we decided that Greg would focus on sales, while I would handle technical issues. Nonetheless, I participate in some sales calls, and I become frustrated when sales don't increase as quickly as I would like.

Repetition

Direct sales requires a lot of repetition. You generally have to give the same pitch over and over to each new potential customer. This conflicts with my natural desire to automate repetitive behavior. When I come across a task that I have to do repeatedly, I usually write a script or subroutine to automate the process.

Part of marketing is to automate the sales message, but when bootstrapping a business with limited capital, much of your marketing message must be delivered personally to potential customers.

Mind-set

I need to find some good books on sales that explains how to get into the correct mind-set.

Michael Cage discusses the need to train non-salespeople for sales. In order to do this, the non-salespeople must learn to think like salespeople.

A couple years ago, I really got into blackjack. To be successful at blackjack requires basically becoming a computer. You must keep track of the count, and bet and play exactly as the rules (based on calculated odds) that you've memorized tell you that must. You must not become emotional after losing a lot of money when doubling after splitting (though you may want to feign being upset for the pit boss's benefit). What happens during a single hand is irrelevant. Your goal is to grind out a profit by having a slight advantage over the house based on the rules and your ability to vary play based on the count.

A fantastic book on blackjack is Million Dollar Blackjack. It describes how Ken Uston and his team won $4,000,000, persevering through lawsuits, pit bosses, the gambling commission, and malfunctioning electronic equipment (they used some computerized counting gear). Business tales are often similar. For example, Joe Kraus's story about how Excite got the deal to be featured in Netscape Navigator is a story about persistence in the face of rejection.

Is there a way to reconcile the techie mind-set with a salesperson's world?

business | Permanent Link

Fri, 29 Oct 2004

Motorola: Terrible Customer Service

I sent the following email to the Motorola Complaint department this morning. It seems I'm not the only one who has suffered from Motorola's decision to completely isolate their customer service department from those in the company who do more than simply answer the phone. Pat Keene has set up a blog called Hello Moto. Where's my phone? detailing roughly the same experience I describe below. If your call center is in India, and your repair center is in Tennessee, it's not like the CSR can walk over to the shipping department or a technician to get an actual answer. I got the idea to copy Stu Reed from Pat.

Date: Fri, 7 Sep 2007 10:55:32 -0700
From: "Christian G. Warden" 
To: d2d001@motorola.com
Cc: stu.reed@motorola.com
Subject: Customer Service Breakdown (RMA #XXXXXXXXXX)

Sir or Madam,
I sent my Moto Q in for repair at the end of July with RMA #xxxxxxxxxx
(zip code xxxxx).  It was received by Motorola on July 31.  According to
the IDEN website, the replacement was sent back to me on August 17.  The
Fedex web site confirms that it received notification of a package
shipped Overnight Priority with tracking number xxxxxxxxxxxx, but has no
record of actually receiving the package.

Having not received the package after one week, I called customer
service at 1-800-453-0920 on Friday, August 24.  I was given case number
xxxxxxxx, and was told that I would receive a call back within 24 hours
from the repair facility.  I didn't receive a call until 3 days later,
on Monday, August 27, and I was told that it was a "courtesy call", that
the caller didn't have any information about my phone, and that I would
receive a call within 24 hours.  I never received that call.

I have since called the customer service number four or five more times.
Every time, I am told that I will receive a call within 24 hours, but I
have never received another call since the courtesy call.  During one
call, I was transfered to the voice mail at repair facility.  I left a
message, but alas, did not receive a call back.

On September 1, I submitted a request through your web site (Incident
#xxxxxx-xxxxxx) explaining my situation and my inability to get through
to someone regarding the whereabouts of my phone.  I received an email
response saying that I would receive a call back.  I didn't.

On Tuesday, September 4, I called 1-800-331-6456 as suggested in the
response to by online request.  I was transfered to same call center I
had spoken with on numerous occasions prior.  I explained the situation
and was given a new case number, xxxxxxxx, and once again told that I
would receive a call within 24 hours.  I didn't receive that call.

On September 5, I received an automated response that my online request
had been closed as "SOLVED".  I responded that it was indeed not solved,
and that I still needed someone to contact me.  I received an email
response, "Once again, we apologize for any inconvenience this delay may
cause you. You will be contacted back to the contact phone number you
provided for accurate assistance with your repair."

This morning I called customer service once again, explained the
situation, and was told that they would contact the repair facility to
have them contact me.  I explained that this process obviously does not
work because the repair facility never calls me.  I asked to speak to
someone at the repair facility, and was told that no phone number was
available.  I asked to speak to the person responsible to managing the
repair facility, and was told that no such person was available.  I
asked for the phone number of the Chief Operations Officer, or a person
of a similar role in your organization, and was told that such
information was not available.  Finally, I simply asked to speak with
the supervisor in the call center.  I was told that the supervisor,
whose name the CSR ("Nicole", #351359) refused to give me, was
preoccupied and would call me within 10 minutes.  I didn't receive that
call either.

Due to the malfunctioning phone, and inability to get it replaced in a timely
manner, I have been without a phone for about six weeks, and have continued to
incur charges from my carrier, Sprint, during this period.

Your assistance in resolving this issue would be greatly appreciated.

Regards,
Christian Warden
(temporary) mobile: 310.xxx.xxxx
home: 408.xxx.xxxx

Update (09/23/2007): After I sent the email above, I received a call from Dorothy Hodges in the consumer advocacy department. She was very pleasant and eager to resolve my problem. She ordered me another Q, but they weren't in stock so she sent me a RAZR2 as a loaner. She also sent me a bluetooth headset as a sorry-we-screwed-up consolation token.

Upon receiving the RAZR, I checked my voicemail, and I had two messages from the management of my apartment complex. It turns out the original replacement phone was devlivered, but that Fedex didn't leave a notice, and that the tracking number Motorola recorded was incorrect. So last Monday I called Sprint with Dorothy and activated the Q. She requested a credit on my behalf since I had been without a phone for six weeks while still paying the bill. I managed to get them from their original offer of 100 minutes to $10 to $30, at which point I accepted.

business | Permanent Link

Thu, 28 Oct 2004

Find a Doctor or Mechanic at Insider Pages

I'm becoming a big fan of Insider Pages. I've used to find a mechanic and dentist this week. It's still a new service, so there aren't a ton of reviews yet, but they're off to a good start. I don't think they have too much of the smarts built in yet, e.g. the opinions of people with those similar to mine should be weighted higher, at least for things like restaurants and bars, but there doesn't seem to be any spam yet, at least.

Since they're still in the early stages and the network effect hasn't taken hold yet, they have a promotion in which you will receive a $5 Startbucks card after writing 5 reviews.

They also provide a wordpress plugin which allows cross-posting on your blog and Insider Pages.

business | Permanent Link

Northern Trust Does Good Economic Research

The smart folks at Northern Trust publish daily economic reports that are short, informative, and full of interesting graphs.

From a recent report on the illusory nature of the recent growth in household net worth:

The rise in household net worth in recent years has largely resulted from inflating prices of corporate equities in the late 1990s, and residential real estate in the past four years. But this rise in household wealth is illusory. The true measure of an increase in the wealth of a nation is the growth in its capital stock. In recent years, growth in our capital stock has slowed and the composition of the slower growth has moved in favor of McMansions and SUVs, which do little to increase the productive capacity of our economy.

From a report titled Investment Implications of the Inevitable Rebalancing of the U.S. Economy or Making Lemonade Out of Lemons, here's a chart of the residential real estate "P/E" ratios, i.e. the ratio of market value to rental rates.

I've setup an RSS feed of their reports so I can read new ones as soon as they're released. It tracks their daily and weekly reports, as well as their economic outlook forecasts which are published monthly.

Update (11/09/2008): I've redirected northerntrust.rss to a Yahoo Pipe that aggregates Northern Trust's own RSS feeds. It does not include their press releases, however.

business | Permanent Link

Sun, 24 Oct 2004

How To Vote on November 2nd in California

I was planning on writing up a summary of how you should vote on each of the propositions on the California ballot in the upcoming election. Steve Friedl beat me to it though, and since I agree with him, in general, I'll limit my comments mostly to where I disagree and the emphasis of points I concur with.

Prop 59

Public Records. Open Meetings

Yes - public disclosure of government actions is good.

Prop 60

Election Rights of Political Parties

Yes - American elections are broken in general. The current system makes it almost impossible for third-party candidates to be elected. We really need to move to a system in which voters can vote for multiple candidates such as approval voting or even better, Condorcet voting in which voters rank the candidates.

Prop 60 does nothing to improve the situation, but, at least, keeps third-party candidates on the ballot.

Prop 60A

Surplus Property

Yes - Requires the government to pay off debt when they have a garage sale. I'd prefer they give me back my money, but this at least puts it to good use.

Prop 61

Children's Hospital Projects

No - Steve's right

The first thing I do when evaluating a bond measure is to look in the argument supporting it: if it says in capital letters "DOES NOT RAISE TAXES", I automatically vote no. This is like saying that using your credit card DOES NOT TAKE CASH OUT OF YOUR WALLET, and if they will be intellectually dishonest on this, I won't believe anything else they say either.

Politicians are idiots. They think they can borrow money without any way to pay it back.

Prop 62

Elections. Primaries.

No - I'm not opposed to open primaries. If political parties want to nominate candidates in a closed matter, they can figure out a way to do so not at public expense. I'm against 62 because it limits general elections to two candidates. (The opposition's repetitions about the Klu Klux Klan are absurd, though.)

Prop 63

Mental Health Services Expansion

No - Tax the rich to pay for others' medical problems? No thanks.

Prop 64

Limits on Private Enforcement of Unfair Business Competition Laws

Yes - This will require plaintiffs to show actual damages in order to sue for "unfair business competition". There are probably lots of things other than fraud encompassed by "unfair business competition" that shouldn't be illegal, but that will have to wait for another election, I guess.

Prop 1A/65

Local Government Funds/Revenues

Yes on both - I don't know that local government is any more fiscally responsible than state government, but I suppose they can be held accountable easier.

Prop 66

Limitations on "Three Strikes" Law. Sex Crimes. Punishment.

Yes - It doesn't get the people in prison for victim-less crimes out, but, at least, keeps me from paying as much for their incarceration. As the opponents point out, it would let out some nasty criminals who are currently imprisoned for less serious crimes, but who have already served their time for their more serious felonies. If their earlier sentences weren't long enough, sentence guidelines for such felonies should be changed. This proposition does double the sentences for sex crimes against children.

Prop 67

Emergency Medical Services. Telephone Surcharge.

No - There is already a 10% city tax on my phone. I don't want to pay 3% more. I pay for my medical care. If you came to my door saying I had to pay for your medical care, I'd tell you to get lost. If doctors aren't being paid, they should find a new job, new customers, or start a charity to raise money for them.

Prop 68

Non-Tribal Commercial Gambling Expansion

Yes - I have mixed feelings about this one. Since Indian reservations aren't really sovereign nations, in that their "citizens" are also U.S. citizens and are, as far as I know, entitled to the same tax-payer funded benefits, they should pay the same taxes imposed on the rest of us. This initiative doesn't impose the same taxes, but would collect significant revenues.

Alternatively, it loosens the restrictions on gambling, but not in a free market way, granting rights to some existing enterprises.

So, 68 gets an unenthusiastic Yes.

Prop 69

DNA Samples. Collection. Database.

No - I don't think this is terribly prone to abuse, but I don't like the idea of treating people who are arrested the same as those convicted.

Prop 70

Tribal Gaming Compacts. Exclusive Gaming Rights.

Yes - Another anti-free market initiative that gives Indians a monopoly on gambling in exchange for, apparently, unaudited tax revenue.

Prop 71

Stem Cell Research

No - Stem Cell research should be done by private enterprises using private funding.

Prop 72

Health Care Coverage Requirements

No - Employees should negotiate with employers for health care benefits that they want.

culture | Permanent Link

Sat, 09 Oct 2004

Tivo Sucks

...a little

Neanderthal Tivo

My Tivo died last weekend. I suppose I'm not quite a typical Tivo owner. I have an old TV with just an RF input. Up until a few months ago, when I moved to Northridge, my only television source was an antenna. I had my Tivo hooked up to my TV, and had my DVD player plugged into my Tivo. My Tivo was configured to think that my DVD player was a satellite receiver.

There were a couple nice things about this setup. I could start a movie on the DVD player, and then use the Tivo remote to control it, allowing me to use the instant replay feature, for example. I could also record DVDs to the Tivo by setting up a manual recording. There are a number of downsides to such a setup, though. I couldn't watch DVDs while the Tivo was recording something. Also, a number of Tivo features didn't work correctly. Tivo stopped recording recommendations after I setup the satellite receiver (DVD player) and some of the search features stopped working. I think it would only search the satellite channel list, even though I had setup My Channels to only include a single channel from the satellite input, 100 - Pay-per-view Previews, that I used to watch DVDs on. I also couldn't take advantage of multi-channel digital audio on DVDs, though that didn't matter with my RF-only TV.

Movin' on Up

I decided to buy a stereo a few weeks ago. I've never owned a real stereo. I've had a sub-$200 bookshelf stereo for about 10 years and the CD player no longer liked playing the first three tracks of most CDs. I was thinking about getting one of those home-theater-in-a-box systems because I figured it would be good for listening to music as well as watching movies. Music is much more important to me than movie sounds effects, though, so I wanted to make sure that music would sound good on the system.

On a Sunday afternoon, I went down to Best Buy with two CDs, Idlewild's The Remote Part and Tom McRae's self-titled. First thing I learned, music sounds like crap on those little speakers that come with those home-theater-in-a-box systems. Ideally, the store would allow customers to hook up any receiver to any CD or DVD player and to any speakers. It didn't seem to work like that at Best Buy, though. So I picked up a Sony Receiver, STR-DE897, assuming it would probably sound good and that I could always return it if I had any problems. Next, I had to decide on speakers. They have a bunch of speakers hooked up to receivers and CD players that have about 6 music tracks you can choose from. After listening to the same tracks over and over, I decided on a pair of Sony floor speakers that sounded good and weren't too expensive. I found a sales guy and told him which speakers I wanted, and he said they didn't have any. Here's a recommendation for brick-and-morter retailers: If you don't have somethingn to sell, take it off the shelf. If I wanted to order something and wait a few days before taking it home, I would've bought it online from the cheapest store.

I bought the receiver since it was a pretty good deal. There was a 10% and it was an open box, so the price was about $270. Next, I went to Circuit City to check out their speakers. (I also went to Good Guys, but quickly left after checking the prices. This is my first real stereo; I'm not fanatical yet.) I spent a long time listening to different speakers and decided on a pair of Polk Audio floor speakers. It should be noted that Circuit City has a much better selection of songs in their test systems. I listened to Snow Patrol and Franz Ferdinand song a number of times. There weren't any sales people working in the audio section, so the car audio guy (he apparently doesn't know anything about audio equipment that can't easily be mounted in an automobile) wrote down the item number and told me to take it to the register. Not having learned my lesson, I was frustrated, though not terribly surprised, to learn that Circuit City only had a single speaker of the model I wanted in stock. So I went home with just a receiver.

I hooked up the receiver to my Tivo and little bookshelf speakers, and ordered the speakers I wanted from circuitcity.com. After doing lots of research trying to determine if there's any possible way for copper wires that come in fancy packaging to sound better, I went to Home Depot to buy some 16-gauge wire.

After getting the new speakers setup, I decided to get the DVD player hooked up directly to the receiver so I could watch DVDs while the Tivo is recording and see how the digital audio compared to running audio over RCA cables through the Tivo. Back at Best Buy, I bought an RF modulator and a digital coax cable. Next lesson, "digital coax" cables are the same as normal RCA coax cables, so that $12 cable is going back to Best Buy, replaced by one from the 99 cents store.

Death of a Tivo

After getting everything hooked up, and powering everything back up, I found that the Tivo had died. Black screen, green light. That's it.

Pressing the Tivo button on the remote made the green light flicker, but couldn't wake the box from it's sleep. I tried hooking everything up how it had been previously thinking that maybe the Tivo got confused when the inputs had changed, but no luck.

My Tivo is just over two years old, long out of warranty. I have paid for the lifetime service, so I definitely wanted to get it repaired, and not have to buy a new Tivo. I opened up the Tivo and found that the hard drive was spinning up, but it didn't sound like it was seeking at all. I pulled the hard drive out, stuck it in my computer, and was able to make a backup using MFS Tools. I emailed the guys at weaKnees, who sell Tivo upgrade an repair parts, and asked if I could bring it. They said it sounded like a bad power supply and that they didn't have any replacements.

I called Tivo to see how I could go about getting it repaired. I hoped that they had local authorized repair shops that I could drop it off at and, hopefully, have it back within a few days. Tivo has an obnoxious voice response system. Automated telephone systems that make you press a myriad of buttons are bad enough, but those that make you speak to them, which I'm sure the manufacturer claims is more satisfying to customers, annoy the hell out of me. After finally navigating the system to the place where I tell the system I wanted to speak to a human, I was told there was a 20 minute wait.

The tech support guy, of course, made me go through the motions of unplugging and replugging the Tivo, pressing the Tivo button on the remote, and even made me replace the coax cable between the Tivo and the TV, not convinced that the cable was OK despite the fact that the television worked fine with the Tivo unplugged and the signal passing through it.

Eventually, we got to the point where he told me I could them my Tivo and $79 and get a replacement. I suppose this is a pretty good deal for a device that's out of warranty. Seventy-nine dollars is also the amount that Best Buy charges for their four-year insurance policy (not an insurance policy in Kansas) on a Tivo. I've never worked in the insurance industry, but it sounds like Best Buy, or the insurance underwriter, would make a good profit off such policies.

FedEx Kinko's shipped my Tivo to Louisville, KY for $10.05. The best thing about the presendential debates is that I can watch them commercial-free without a Tivo.

culture | Permanent Link

Sat, 02 Oct 2004

Free iPod Shuffle from ex24

Thanks to MyMoneyBlog, I will soon be the owner of an iPod Shuffle courtesy of ex24. ex24 operates order books for a handful of securities that allow it's customers to trade odd lots (less than 100 shares) amongst themselves. Basically, they are both an ECN, like Island (now INET) or Archipelago, and a broker/dealer. The description of how quoting works on their order books makes for somewhat interesting reading.

At times when the CQS is not operating, ex24 raises its offer quote by one cent each time members collectively buy from ex24 for a net 99 shares in that after-hours session and lower its bid quote each time members collectively sell to ex24 for a net 99 shares in the after-hours session. If ex24 raises (lowers) its offer (bid) quote due to after-hours purchases (sales), it increases (lowers) its bid (offer) quote by the same amount to maintain the same spread width. After ex24 has raised (lowered) its offer (bid) quote by five cents it either keeps its quote at that price or deletes its offer (bid) quote, depending on market conditions.

The decision whether to freeze the quote or delete it is effected pursuant to an algorithm that operates automatically. This trading parameter is intended to provide ex24 a degree of protection from the risk associated with posting quotes during times when CQS is not operating. Additionally, ex24 reserves the right to withdraw its quotes at any time during the after-hours session, which it might do in response to significant news affecting the price of a security traded on ex24 at times when CQS is not operating.

Finally, ex24 may program its algorithm to refrain from posting its offer (bid) quote if customer orders to sell (buy) at or near the CQS offer quote reside on the ATS. ex24 is in the process of determining the amount of customer orders that might cause it to refrain from posting a quote. Regardless of the metric chosen by ex24, the algorithm which determines whether or not to disseminate a quote operates automatically.

If you open an account, fund it with $500, and make at least one trade, they'll send you a free iPod Shuffle. Apparently, it's possible to use the Shuffle with Linux so I'm a happy camper.

business | Permanent Link

Overcoming Procrastination

Steve Pavlina, the founder of Dexterity Software, has written an excellent article on overcoming procrastination. If you don't have time to read it right now, here are the major points to avoid procrastinating on a given task.

  1. Realize that you don't have to do it.
  2. Start on a small subtask.
  3. It doesn't have to be perfect.
  4. Do things you enjoy first.
[via Business Opportunities Weblog]

business | Permanent Link

Fri, 01 Oct 2004

Airplay Exposes Videos Watched in Chrome's Incognito Mode

iOS may expose your guilty pleasures by showing the names of videos watched in Chrome's Incognito mode.



After exiting incognito mode, the name of a video viewed is still shown.



Killing chrome removes the name of the video.

tech | Permanent Link

Clusty Asks "Which Hat?"

Clusty, the newest next Google, asks you which hat you're wearing after you do a search by grouping related results into clusters. So you can search for eclipse, and it will give you the option of seeing results related to lunar eclipses or software.

Under Customize!, they have a blogs channel, and even a slashdot channel.

tech | Permanent Link

Correct Use of FROM Clauses

There's an interesting article by Stéphane Faroult on ONLamp about when to use subselects rather than JOINs in SQL queries. He says that only tables from which you are returning columns or those tables necessary to join such tables should appear in the FROM clause. All other tables used to limit the rows returned should appear in subselects in the WHERE clause. He rewrites


select distinct a.CUSTOMER_ID, a.CUSTOMER_NAME
from CUSTOMERS a,
     ORDERS    b
where a.ZIP_CODE in ...
  and b.ORDERED_DATE >= ...
  and b.CUSTOMER_ID = a.CUSTOMER_ID
order by a.CUSTOMER_NAME

as


select a.CUSTOMER_ID, a.CUSTOMER_NAME
from CUSTOMERS a
where a.ZIP_CODE in ...
  and a.CUSTOMER_ID in (select b.CUSTOMER_ID
                        from ORDERS b
                        where b.ORDERED_DATE >= ...)
order by a.CUSTOMER_NAME

He also gives another way to rewrite the query as an uncorrelated subquery. (Read the article for an explanation of the difference between correlated and uncorrelated queries.)

Having done almost exclusively MySQL development, I have yet to ever use subselects, but I'll definitely keep Faroult's article in mind for when I do.

tech | Permanent Link

Sun, 19 Sep 2004

JavaHMO and Debian

JavaHMO is an application that allows you to use the Tivo Home Media Option on platforms other than Windows and OS X. (It works on those platforms, too.) The RedHat packages available on the SourceForge site install fine under Debian after being converted by alien, but the RedHat init script doesn't work, so I wrote an init script for Debian.

The init script runs JavaHMO as the javahmo user, so you must create the user before using it.
# adduser --system --home /usr/share/javaHMO --shell /bin/false --group javahmo

Update: I created a proper Debian package for JavaHMO that doesn't require using alien to convert the RPM.

tech | Permanent Link

Sat, 18 Sep 2004

Sender Address Verification in the Real World

I've been using sender address verification callbacks for a long time. It helps eliminate a lot of spam by checking if the sender's address is deliverable. Unfortunately, there are a number of systems that send mail with an invalid envelope sender. These are often generated by scripts on a web server where the sender defaults to the-apache-user@the.web.server.name. There are also a number of misconfigured mail server, mostly IMail installations, that do not accept messages with null senders. This not only prevents their users from receiving bounce messages, but also prevents sender address verification from working.

Up until yesterday, I've rejected messages at RCPT time that fail sender address verification. Trying to deal with the number of false positives for a significant number of users has proven to be too dificult. So I decided to continue using sender address verification, but incorporate the result into an overall SpamAssassin score.

Andrew, on the exim-users list provided a helpful Exim ACL snippet which I modified a bit and came up with the following:

acl_callout_test:
  warn set acl_m6 = TEMP
  accept verify = sender/callout=60s,random
    set acl_m6 = OK
  warn set acl_m6 = FAIL

acl_check_rcpt:
  warn acl = acl_callout_test
  warn message = X-Sender-Verification: $acl_m6

This adds an X-Sender-Verification header which I then check for in SpamAssassin.

header    POSTICA_SENDER_ADDRESS_FAIL   X-Sender-Verification =~ /FAIL/
describe  POSTICA_SENDER_ADDRESS_FAIL   Sender Address Verification Failure
score     POSTICA_SENDER_ADDRESS_FAIL   2.0

header    POSTICA_SENDER_ADDRESS_TEMPFAIL   X-Sender-Verification =~ /TEMP/
describe  POSTICA_SENDER_ADDRESS_TEMPFAIL   Sender Address Verification Temp Failure
score     POSTICA_SENDER_ADDRESS_TEMPFAIL   1.0

I may have to tweak the scores, but so far, it's working pretty well.

tech » mail | Permanent Link

Carrie Graber, Artist

I don't know much about art. In fact, the only artist whose work I can think of that I like is Salvadore Dalí. (I quite enjoyed visiting the Dalí Museum in Florida five years ago.)

I stumbled across Carrie Graber's work while checking out the web site for Scala Editions, who recently became a Postica customer. I was immediately taken with her paintings. I don't know what the appropriate terms are when discussing art, but I quite like her use of lighting. The pictures with people, mostly women, convey a sense of anticipation; it's like they are scenes from movies I'd like to see.

culture | Permanent Link

Tue, 14 Sep 2004

Introduction to Mailgraph

Over at ONLamp, David Ky describes how to setup Mailgraph.

Mailgraph is a great tool for monitoring your mail servers. It generates graphs showing how many messages are passing through your servers as well as the number of spam and viruses blocked. Here's a sample graph:

David didn't mention that there are Debian packages for Mailgraph. I've also modified it to work with Exim log files. The Mailgraph for Exim packages are available from the Postica site.

tech » mail | Permanent Link

Search Hats

Don Park has an idea about wearing different hats while searching. [via John Battelle's Searchblog]

For example, if I search for 'Eclipse' while wearing the 'Software Developer' hat, I should get Eclipse IDE related links before links related to the astrophysical phenenomon. If even I was interested in the later, results I get back should be different depending on whether I am wearing a Physicist's hat or a Photographer's hat.

That example is rather silly, though. If you're searching for the Elipse IDE, obviously you can just search for eclipse ide or eclipse java development, and if you're searching for astronomical eclipses, you'd search for something like lunar eclipse.

What would be useful is an "I'm not shopping for anything" option. There are many sites that get in the way in Google's search results when you're trying to research certain consumer electronics, for example.

tech | Permanent Link

Sat, 11 Sep 2004

Beer Tastes Good

Apparently, it is now possible to inhale alcohol. I'm not sure why anyone would want to, though. I can't imagine going into a pub and having the bartender put down a pint of your favorite vaporized beer.

That sounds about as likely as someone listening to a crappy version of their favorite song on their mobile phone.

business | Permanent Link

Thu, 09 Sep 2004

Are All Web Browsers Broken?

According to Derek, hrefs such as <a href="https:/path"> should be valid and link to the same site using https as the protocol.
This doesn't work in Mozilla, which converts such links to https://path/, nor in links which interprets it as http://domain.tld/https:/path.
Interestingly, it does work in Mozilla if the link is the same protocol as you're currently using.

If it worked, it would make web development easier. You wouldn't have to use tokens in html templates for the server name to avoid hard coding in the production server's domain. I'm sure there are other's who've used output buffering hacks to rewrite relative links.

So, what's the deal? Are all web browsers broken?

tech | Permanent Link

Restricting Incoming Mail to MX Hosts using Sendmail

When using an external server or servers to filter your mail, you want to make sure that spammers and worms cannot bypass the filtering servers and deliver unsanitized messages directly to the destination mail server. In order to do this, the destination MTA must check the IP address of the server trying to deliver a message and verify that it is one of the allowed hosts.

If you only have one domain on your sendmail server, or all domains use the same filtering hosts, this is fairly easy to accomplish using either a firewall, TCP wrappers, or a number of not-so-elegant entries in your access table. If you host a number of domains, these methods may not be acceptable.

I have written some sendmail rules to restrict delivery on a per-domain basis. Groups of scanning hosts, known as a scangroup, can be setup and each domain can belong to one scangroup. If a domain does not belong to a scangroup, messages to that domain are accepted as usual.

Two new maps need to be created, scandomain and scanhost. The scandomain map lists each domain that uses a scangroup and scanhost lists the IP addresses of each host in a scan group. The left hand side is the domain and IP address, respectively, and the right hand side is the scangroup name for both maps.

The rules which reject unauthorized delivery are added to the Local_check_rcpt ruleset so the rejection occurs after each RCPT TO: command. Because the mail server may also be used for message submission by MUAs, it accepts messages from authenticated users and IPs explicitly allowed to relay in either the relay-domains file or access map.

To enable scangroups, add the contents of scangroups.mc to the bottom of your sendmail.mc and regenerate your sendmail.cf. Don't forget to run makemap after setting up your scandomain and scanhost files.

If you're looking for a service to filter spam and viruses from your mail before they get to your mail server, please check out Postica, which I developed.

Updated - 5/2/2005: I updated the ruleset to reject messages with a temporary failure so that mail doesn't accidentally get rejected if DNS is being changed, and still points directly at the destination server.

tech » mail | Permanent Link

Thu, 02 Sep 2004

How To Print a Book From Postscript

Here's how to print some documentation as a book. The goal was to print two pages per sheet double sided without having the luxury of a printer that supports duplex printing.

In this example, I'm printing about 50 pages (Chapter 5) from the sendmail documentation.

psselect -p34-85 op.ps | psbook | psnup -2 | pstops 2:-0 | lpr

So we use psselect to pick the pages we want to print. Then, we use psbook to rearrange the pages so they end up in the right order in the finished booklet. The psnup utility is used to to print two pages per sheet, and finally, pstops selects just the odd pages in reverse order.

In the second step, you feed the pages back through your printer using the manual feeder. On my printer, a LaserJet 1200, the manual feeder pulls page from the top, the same as the tray. That is why I used pstops to rearrange the pages. If your manual feed pulls from the bottom, you can print the odd pages in the normal order.

psselect -p34-85 op.ps | psbook | psnup -2 | psselect -o | lpr

Now, we just put the pages in the manual feeder and print the even pages.

psselect -p34-85 op.ps | psbook | psnup -2 | psselect -e | lpr

P.S (Ha!) I think pstops can do everything that psselect, psbook, and psnup do, but I didn't bother learning the syntax.

tech | Permanent Link

The state is that great fiction by which everyone tries to live at the expense of everyone else. - Frederic Bastiat